mock_redis 0.19.0 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|