mock_redis 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/.travis.yml +9 -10
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -2
- data/README.md +2 -2
- data/lib/mock_redis/database.rb +6 -5
- data/lib/mock_redis/geospatial_methods.rb +10 -18
- data/lib/mock_redis/hash_methods.rb +4 -4
- data/lib/mock_redis/indifferent_hash.rb +0 -8
- data/lib/mock_redis/list_methods.rb +2 -2
- data/lib/mock_redis/pipelined_wrapper.rb +25 -6
- data/lib/mock_redis/set_methods.rb +15 -4
- data/lib/mock_redis/stream.rb +62 -0
- data/lib/mock_redis/stream/id.rb +53 -0
- data/lib/mock_redis/stream_methods.rb +87 -0
- data/lib/mock_redis/string_methods.rb +5 -8
- data/lib/mock_redis/transaction_wrapper.rb +25 -12
- data/lib/mock_redis/utility_methods.rb +1 -1
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset_methods.rb +2 -2
- data/mock_redis.gemspec +6 -5
- data/spec/commands/geodist_spec.rb +8 -4
- data/spec/commands/geohash_spec.rb +4 -4
- data/spec/commands/geopos_spec.rb +4 -4
- data/spec/commands/get_spec.rb +1 -0
- data/spec/commands/hdel_spec.rb +2 -2
- data/spec/commands/mget_spec.rb +34 -15
- data/spec/commands/mset_spec.rb +14 -0
- data/spec/commands/pipelined_spec.rb +52 -0
- data/spec/commands/spop_spec.rb +15 -0
- data/spec/commands/watch_spec.rb +8 -3
- data/spec/commands/xadd_spec.rb +102 -0
- data/spec/commands/xlen_spec.rb +20 -0
- data/spec/commands/xrange_spec.rb +141 -0
- data/spec/commands/xrevrange_spec.rb +130 -0
- data/spec/commands/xtrim_spec.rb +30 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/redis_multiplexer.rb +17 -1
- data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_lists.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_sets.rb +2 -0
- data/spec/support/shared_examples/only_operates_on_zsets.rb +2 -0
- data/spec/transactions_spec.rb +17 -29
- metadata +38 -12
- data/spec/commands/hash_operator_spec.rb +0 -21
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'mock_redis/assertions'
|
2
|
+
require 'mock_redis/utility_methods'
|
3
|
+
require 'mock_redis/stream'
|
4
|
+
|
5
|
+
# TODO: Implement the following commands
|
6
|
+
#
|
7
|
+
# * xread
|
8
|
+
# * xgroup
|
9
|
+
# * xreadgroup
|
10
|
+
# * xack
|
11
|
+
# * xpending
|
12
|
+
# * xclaim
|
13
|
+
# * xinfo
|
14
|
+
# * xtrim
|
15
|
+
# * xdel
|
16
|
+
#
|
17
|
+
# TODO: Complete support for
|
18
|
+
#
|
19
|
+
# * xtrim
|
20
|
+
# - `approximate: true` argument is currently ignored
|
21
|
+
# * xadd
|
22
|
+
# - `approximate: true` argument (for capped streams) is currently ignored
|
23
|
+
#
|
24
|
+
# For details of these commands see
|
25
|
+
# * https://redis.io/topics/streams-intro
|
26
|
+
# * https://redis.io/commands#stream
|
27
|
+
|
28
|
+
class MockRedis
|
29
|
+
module StreamMethods
|
30
|
+
include Assertions
|
31
|
+
include UtilityMethods
|
32
|
+
|
33
|
+
def xadd(key, entry, opts = {})
|
34
|
+
id = opts[:id] || '*'
|
35
|
+
with_stream_at(key) do |stream|
|
36
|
+
stream.add id, entry
|
37
|
+
stream.trim opts[:maxlen] if opts[:maxlen]
|
38
|
+
return stream.last_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def xtrim(key, count)
|
43
|
+
with_stream_at(key) do |stream|
|
44
|
+
stream.trim count
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def xlen(key)
|
49
|
+
with_stream_at(key) do |stream|
|
50
|
+
return stream.count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def xrange(key, first = '-', last = '+', count: nil)
|
55
|
+
args = [first, last, false]
|
56
|
+
args += ['COUNT', count] if count
|
57
|
+
with_stream_at(key) do |stream|
|
58
|
+
return stream.range(*args)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def xrevrange(key, last = '+', first = '-', count: nil)
|
63
|
+
args = [first, last, true]
|
64
|
+
args += ['COUNT', count] if count
|
65
|
+
with_stream_at(key) do |stream|
|
66
|
+
return stream.range(*args)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def with_stream_at(key, &blk)
|
73
|
+
with_thing_at(key, :assert_streamy, proc { Stream.new }, &blk)
|
74
|
+
end
|
75
|
+
|
76
|
+
def streamy?(key)
|
77
|
+
data[key].nil? || data[key].is_a?(Stream)
|
78
|
+
end
|
79
|
+
|
80
|
+
def assert_streamy(key)
|
81
|
+
unless streamy?(key)
|
82
|
+
raise Redis::CommandError,
|
83
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -85,14 +85,11 @@ class MockRedis
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def get(key)
|
88
|
+
key = key.to_s
|
88
89
|
assert_stringy(key)
|
89
90
|
data[key]
|
90
91
|
end
|
91
92
|
|
92
|
-
def [](key)
|
93
|
-
get(key)
|
94
|
-
end
|
95
|
-
|
96
93
|
def getbit(key, offset)
|
97
94
|
assert_stringy(key)
|
98
95
|
|
@@ -159,6 +156,7 @@ class MockRedis
|
|
159
156
|
def mget(*keys)
|
160
157
|
assert_has_args(keys, 'mget')
|
161
158
|
|
159
|
+
keys.flatten!
|
162
160
|
keys.map do |key|
|
163
161
|
get(key) if stringy?(key)
|
164
162
|
end
|
@@ -170,6 +168,8 @@ class MockRedis
|
|
170
168
|
|
171
169
|
def mset(*kvpairs)
|
172
170
|
assert_has_args(kvpairs, 'mset')
|
171
|
+
kvpairs = kvpairs.first if kvpairs.size == 1 && kvpairs.first.is_a?(Enumerable)
|
172
|
+
|
173
173
|
if kvpairs.length.odd?
|
174
174
|
raise Redis::CommandError, 'ERR wrong number of arguments for MSET'
|
175
175
|
end
|
@@ -201,6 +201,7 @@ class MockRedis
|
|
201
201
|
end
|
202
202
|
|
203
203
|
def set(key, value, options = {})
|
204
|
+
key = key.to_s
|
204
205
|
return_true = false
|
205
206
|
options = options.dup
|
206
207
|
if options.delete(:nx)
|
@@ -231,10 +232,6 @@ class MockRedis
|
|
231
232
|
return_true ? true : 'OK'
|
232
233
|
end
|
233
234
|
|
234
|
-
def []=(key, value, options = {})
|
235
|
-
set(key, value, options)
|
236
|
-
end
|
237
|
-
|
238
235
|
def setbit(key, offset, value)
|
239
236
|
assert_stringy(key, 'ERR bit is not an integer or out of range')
|
240
237
|
retval = getbit(key, offset)
|
@@ -11,12 +11,12 @@ class MockRedis
|
|
11
11
|
def initialize(db)
|
12
12
|
@db = db
|
13
13
|
@transaction_futures = []
|
14
|
-
@
|
14
|
+
@multi_stack = []
|
15
15
|
@multi_block_given = false
|
16
16
|
end
|
17
17
|
|
18
18
|
def method_missing(method, *args, &block)
|
19
|
-
if
|
19
|
+
if in_multi?
|
20
20
|
future = MockRedis::Future.new([method, *args])
|
21
21
|
@transaction_futures << future
|
22
22
|
|
@@ -35,23 +35,25 @@ class MockRedis
|
|
35
35
|
super
|
36
36
|
@db = @db.clone
|
37
37
|
@transaction_futures = @transaction_futures.clone
|
38
|
+
@multi_stack = @multi_stack.clone
|
38
39
|
end
|
39
40
|
|
40
41
|
def discard
|
41
|
-
unless
|
42
|
+
unless in_multi?
|
42
43
|
raise Redis::CommandError, 'ERR DISCARD without MULTI'
|
43
44
|
end
|
44
|
-
|
45
|
-
|
45
|
+
pop_multi
|
46
|
+
|
46
47
|
@transaction_futures = []
|
47
48
|
'OK'
|
48
49
|
end
|
49
50
|
|
50
51
|
def exec
|
51
|
-
unless
|
52
|
+
unless in_multi?
|
52
53
|
raise Redis::CommandError, 'ERR EXEC without MULTI'
|
53
54
|
end
|
54
|
-
|
55
|
+
pop_multi
|
56
|
+
return if in_multi?
|
55
57
|
@multi_block_given = false
|
56
58
|
|
57
59
|
responses = @transaction_futures.map do |future|
|
@@ -68,12 +70,21 @@ class MockRedis
|
|
68
70
|
responses
|
69
71
|
end
|
70
72
|
|
73
|
+
def in_multi?
|
74
|
+
@multi_stack.any?
|
75
|
+
end
|
76
|
+
|
77
|
+
def push_multi
|
78
|
+
@multi_stack.push(@multi_stack.size + 1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def pop_multi
|
82
|
+
@multi_stack.pop
|
83
|
+
end
|
84
|
+
|
71
85
|
def multi
|
72
|
-
if @in_multi
|
73
|
-
raise Redis::CommandError, 'ERR MULTI calls can not be nested'
|
74
|
-
end
|
75
|
-
@in_multi = true
|
76
86
|
if block_given?
|
87
|
+
push_multi
|
77
88
|
@multi_block_given = true
|
78
89
|
begin
|
79
90
|
yield(self)
|
@@ -83,6 +94,8 @@ class MockRedis
|
|
83
94
|
raise e
|
84
95
|
end
|
85
96
|
else
|
97
|
+
raise Redis::CommandError, 'ERR MULTI calls can not be nested' if in_multi?
|
98
|
+
push_multi
|
86
99
|
'OK'
|
87
100
|
end
|
88
101
|
end
|
@@ -95,7 +108,7 @@ class MockRedis
|
|
95
108
|
'OK'
|
96
109
|
end
|
97
110
|
|
98
|
-
def watch(_)
|
111
|
+
def watch(*_)
|
99
112
|
if block_given?
|
100
113
|
yield self
|
101
114
|
else
|
data/lib/mock_redis/version.rb
CHANGED
@@ -11,7 +11,7 @@ class MockRedis
|
|
11
11
|
zadd_options = {}
|
12
12
|
zadd_options = args.pop if args.last.is_a?(Hash)
|
13
13
|
|
14
|
-
if zadd_options
|
14
|
+
if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
|
15
15
|
raise Redis::CommandError, 'ERR XX and NX options at the same time are not compatible'
|
16
16
|
end
|
17
17
|
|
@@ -211,7 +211,7 @@ class MockRedis
|
|
211
211
|
def zscore(key, member)
|
212
212
|
with_zset_at(key) do |z|
|
213
213
|
score = z.score(member.to_s)
|
214
|
-
score
|
214
|
+
score&.to_f
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
data/mock_redis.gemspec
CHANGED
@@ -6,9 +6,9 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.version = MockRedis::VERSION
|
7
7
|
s.license = 'MIT'
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ['
|
10
|
-
s.email = ['
|
11
|
-
s.homepage = 'https://github.com/
|
9
|
+
s.authors = ['Shane da Silva', 'Samuel Merritt']
|
10
|
+
s.email = ['shane@dasilva.io']
|
11
|
+
s.homepage = 'https://github.com/sds/mock_redis'
|
12
12
|
s.summary = 'Redis mock that just lives in memory; useful for testing.'
|
13
13
|
|
14
14
|
s.description = <<-MSG.strip.gsub(/\s+/, ' ')
|
@@ -21,10 +21,11 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
22
22
|
s.require_paths = ['lib']
|
23
23
|
|
24
|
-
s.required_ruby_version = '>= 2.
|
24
|
+
s.required_ruby_version = '>= 2.4'
|
25
25
|
|
26
26
|
s.add_development_dependency 'rake', '>= 10', '< 12'
|
27
|
-
s.add_development_dependency 'redis', '~>
|
27
|
+
s.add_development_dependency 'redis', '~>4.1.0'
|
28
28
|
s.add_development_dependency 'rspec', '~> 3.0'
|
29
29
|
s.add_development_dependency 'rspec-its', '~> 1.0'
|
30
|
+
s.add_development_dependency 'timecop', '~> 0.9.1'
|
30
31
|
end
|
@@ -80,13 +80,14 @@ describe '#geodist' do
|
|
80
80
|
|
81
81
|
context 'with less than 3 arguments' do
|
82
82
|
[1, 2].each do |count|
|
83
|
-
let(:message) { "ERR wrong number of arguments for 'geodist' command" }
|
84
|
-
|
85
83
|
context "with #{count} arguments" do
|
86
84
|
it 'raises an error' do
|
87
85
|
args = list.slice(0, count)
|
88
86
|
expect { @redises.geodist(*args) }
|
89
|
-
.to raise_error(
|
87
|
+
.to raise_error(
|
88
|
+
ArgumentError,
|
89
|
+
/wrong number of arguments \((given\s)?#{count}(,\sexpected\s|\sfor\s)3?\.\.4\)$/
|
90
|
+
)
|
90
91
|
end
|
91
92
|
end
|
92
93
|
end
|
@@ -98,7 +99,10 @@ describe '#geodist' do
|
|
98
99
|
it 'raises an error' do
|
99
100
|
args = list.slice(0, 5)
|
100
101
|
expect { @redises.geodist(*args) }
|
101
|
-
.to raise_error(
|
102
|
+
.to raise_error(
|
103
|
+
ArgumentError,
|
104
|
+
/wrong number of arguments \((given\s)?5(,\sexpected\s|\sfor\s)3?\.\.4\)$/
|
105
|
+
)
|
102
106
|
end
|
103
107
|
end
|
104
108
|
end
|
@@ -17,13 +17,13 @@ describe '#geohash' do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'returns decoded coordinates pairs for each point' do
|
20
|
-
results = @redises.geohash(key,
|
20
|
+
results = @redises.geohash(key, %w[SF LA])
|
21
21
|
expect(results).to be == expected_result
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'with non-existing points only' do
|
25
25
|
it 'returns array filled with nils' do
|
26
|
-
results = @redises.geohash(key,
|
26
|
+
results = @redises.geohash(key, %w[FF FA])
|
27
27
|
expect(results).to be == [nil, nil]
|
28
28
|
end
|
29
29
|
end
|
@@ -34,7 +34,7 @@ describe '#geohash' do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'returns mixture of nil and coordinates pair' do
|
37
|
-
results = @redises.geohash(key,
|
37
|
+
results = @redises.geohash(key, %w[SF FA])
|
38
38
|
expect(results).to be == expected_result
|
39
39
|
end
|
40
40
|
end
|
@@ -45,7 +45,7 @@ describe '#geohash' do
|
|
45
45
|
before { @redises.del(key) }
|
46
46
|
|
47
47
|
it 'returns empty array' do
|
48
|
-
results = @redises.geohash(key,
|
48
|
+
results = @redises.geohash(key, %w[SF LA])
|
49
49
|
expect(results).to be == [nil, nil]
|
50
50
|
end
|
51
51
|
end
|
@@ -20,13 +20,13 @@ describe '#geopos' do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'returns decoded coordinates pairs for each point' do
|
23
|
-
coords = @redises.geopos(key,
|
23
|
+
coords = @redises.geopos(key, %w[SF LA])
|
24
24
|
expect(coords).to be == expected_result
|
25
25
|
end
|
26
26
|
|
27
27
|
context 'with non-existing points only' do
|
28
28
|
it 'returns array filled with nils' do
|
29
|
-
coords = @redises.geopos(key,
|
29
|
+
coords = @redises.geopos(key, %w[FF FA])
|
30
30
|
expect(coords).to be == [nil, nil]
|
31
31
|
end
|
32
32
|
end
|
@@ -37,7 +37,7 @@ describe '#geopos' do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'returns mixture of nil and coordinates pair' do
|
40
|
-
coords = @redises.geopos(key,
|
40
|
+
coords = @redises.geopos(key, %w[SF FA])
|
41
41
|
expect(coords).to be == expected_result
|
42
42
|
end
|
43
43
|
end
|
@@ -48,7 +48,7 @@ describe '#geopos' do
|
|
48
48
|
before { @redises.del(key) }
|
49
49
|
|
50
50
|
it 'returns empty array' do
|
51
|
-
coords = @redises.geopos(key,
|
51
|
+
coords = @redises.geopos(key, %w[SF LA])
|
52
52
|
expect(coords).to be == [nil, nil]
|
53
53
|
end
|
54
54
|
end
|
data/spec/commands/get_spec.rb
CHANGED
data/spec/commands/hdel_spec.rb
CHANGED
@@ -39,14 +39,14 @@ describe '#hdel(key, field)' do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'supports a variable number of arguments' do
|
42
|
-
@redises.hdel(@key,
|
42
|
+
@redises.hdel(@key, 'k1', 'k2')
|
43
43
|
@redises.get(@key).should be_nil
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'treats variable arguments as strings' do
|
47
47
|
field = 2
|
48
48
|
@redises.hset(@key, field, 'two')
|
49
|
-
@redises.hdel(@key,
|
49
|
+
@redises.hdel(@key, field)
|
50
50
|
@redises.hget(@key, field).should be_nil
|
51
51
|
end
|
52
52
|
|
data/spec/commands/mget_spec.rb
CHANGED
@@ -9,26 +9,45 @@ describe '#mget(key [, key, ...])' do
|
|
9
9
|
@redises.set(@key2, 2)
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
context 'emulate param array' do
|
13
|
+
it 'returns an array of values' do
|
14
|
+
@redises.mget([@key1, @key2]).should == %w[1 2]
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
17
|
+
it 'returns an array of values' do
|
18
|
+
@redises.mget([@key1, @key2]).should == %w[1 2]
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
it 'returns nil for non-string keys' do
|
22
|
+
list = 'mock-redis-test:mget-list'
|
23
23
|
|
24
|
-
|
24
|
+
@redises.lpush(list, 'bork bork bork')
|
25
25
|
|
26
|
-
|
26
|
+
@redises.mget([@key1, @key2, list]).should == ['1', '2', nil]
|
27
|
+
end
|
27
28
|
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
@redises.mget
|
32
|
-
end
|
30
|
+
context 'emulate params strings' do
|
31
|
+
it 'returns an array of values' do
|
32
|
+
@redises.mget(@key1, @key2).should == %w[1 2]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns nil for missing keys' do
|
36
|
+
@redises.mget(@key1, 'mock-redis-test:not-found', @key2).should == ['1', nil, '2']
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns nil for non-string keys' do
|
40
|
+
list = 'mock-redis-test:mget-list'
|
41
|
+
|
42
|
+
@redises.lpush(list, 'bork bork bork')
|
43
|
+
|
44
|
+
@redises.mget(@key1, @key2, list).should == ['1', '2', nil]
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises an error if you pass it 0 arguments' do
|
48
|
+
lambda do
|
49
|
+
@redises.mget
|
50
|
+
end.should raise_error(Redis::CommandError)
|
51
|
+
end
|
33
52
|
end
|
34
53
|
end
|