mock_redis 0.20.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -5
  3. data/.rubocop_todo.yml +1 -1
  4. data/.travis.yml +3 -3
  5. data/CHANGELOG.md +37 -0
  6. data/Gemfile +2 -2
  7. data/LICENSE.md +21 -0
  8. data/README.md +37 -13
  9. data/lib/mock_redis.rb +0 -5
  10. data/lib/mock_redis/database.rb +53 -17
  11. data/lib/mock_redis/future.rb +1 -1
  12. data/lib/mock_redis/geospatial_methods.rb +4 -4
  13. data/lib/mock_redis/hash_methods.rb +22 -14
  14. data/lib/mock_redis/info_method.rb +2 -2
  15. data/lib/mock_redis/multi_db_wrapper.rb +2 -2
  16. data/lib/mock_redis/set_methods.rb +1 -0
  17. data/lib/mock_redis/stream.rb +22 -2
  18. data/lib/mock_redis/stream/id.rb +1 -1
  19. data/lib/mock_redis/stream_methods.rb +14 -1
  20. data/lib/mock_redis/string_methods.rb +28 -13
  21. data/lib/mock_redis/transaction_wrapper.rb +2 -2
  22. data/lib/mock_redis/utility_methods.rb +6 -3
  23. data/lib/mock_redis/version.rb +1 -1
  24. data/lib/mock_redis/zset_methods.rb +52 -9
  25. data/mock_redis.gemspec +1 -2
  26. data/spec/client_spec.rb +12 -0
  27. data/spec/commands/blpop_spec.rb +0 -6
  28. data/spec/commands/brpop_spec.rb +6 -5
  29. data/spec/commands/del_spec.rb +15 -0
  30. data/spec/commands/dump_spec.rb +19 -0
  31. data/spec/commands/exists_spec.rb +34 -5
  32. data/spec/commands/future_spec.rb +11 -1
  33. data/spec/commands/geoadd_spec.rb +1 -1
  34. data/spec/commands/hdel_spec.rb +16 -0
  35. data/spec/commands/hmset_spec.rb +26 -0
  36. data/spec/commands/hset_spec.rb +6 -6
  37. data/spec/commands/keys_spec.rb +17 -0
  38. data/spec/commands/mget_spec.rb +6 -0
  39. data/spec/commands/move_spec.rb +5 -5
  40. data/spec/commands/pipelined_spec.rb +20 -0
  41. data/spec/commands/restore_spec.rb +47 -0
  42. data/spec/commands/scan_spec.rb +9 -0
  43. data/spec/commands/set_spec.rb +12 -2
  44. data/spec/commands/setbit_spec.rb +1 -0
  45. data/spec/commands/setex_spec.rb +16 -0
  46. data/spec/commands/srandmember_spec.rb +1 -1
  47. data/spec/commands/srem_spec.rb +5 -0
  48. data/spec/commands/xadd_spec.rb +20 -0
  49. data/spec/commands/xrange_spec.rb +13 -0
  50. data/spec/commands/xread_spec.rb +50 -0
  51. data/spec/commands/xtrim_spec.rb +6 -0
  52. data/spec/commands/zinterstore_spec.rb +34 -0
  53. data/spec/commands/zpopmax_spec.rb +60 -0
  54. data/spec/commands/zpopmin_spec.rb +60 -0
  55. data/spec/commands/zrange_spec.rb +1 -1
  56. data/spec/commands/zrangebyscore_spec.rb +1 -1
  57. data/spec/commands/zrevrange_spec.rb +1 -1
  58. data/spec/commands/zrevrangebyscore_spec.rb +1 -1
  59. data/spec/commands/zunionstore_spec.rb +33 -0
  60. data/spec/mock_redis_spec.rb +4 -6
  61. data/spec/spec_helper.rb +4 -2
  62. data/spec/support/redis_multiplexer.rb +1 -0
  63. data/spec/transactions_spec.rb +16 -0
  64. metadata +16 -26
  65. data/LICENSE +0 -19
@@ -14,4 +14,16 @@ describe 'client' do
14
14
  redis.connect.should == redis
15
15
  end
16
16
  end
17
+
18
+ context '#disconnect!' do
19
+ it 'responds to disconnect!' do
20
+ expect(MockRedis.new).to respond_to(:disconnect!)
21
+ end
22
+ end
23
+
24
+ context '#close' do
25
+ it 'responds to close' do
26
+ expect(MockRedis.new).to respond_to(:close)
27
+ end
28
+ end
17
29
  end
@@ -27,12 +27,6 @@ describe '#blpop(key [, key, ...,], timeout)' do
27
27
  [@list1, 'one']
28
28
  end
29
29
 
30
- it 'raises an error on subsecond timeouts' do
31
- lambda do
32
- @redises.blpop(@list1, @list2, :timeout => 0.5)
33
- end.should raise_error(Redis::CommandError)
34
- end
35
-
36
30
  it 'raises an error on negative timeout' do
37
31
  lambda do
38
32
  @redises.blpop(@list1, @list2, :timeout => -1)
@@ -26,11 +26,12 @@ describe '#brpop(key [, key, ...,], timeout)' do
26
26
  [@list1, 'two']
27
27
  end
28
28
 
29
- it 'raises an error on subsecond timeouts' do
30
- lambda do
31
- @redises.brpop(@list1, @list2, :timeout => 0.5)
32
- end.should raise_error(Redis::CommandError)
33
- end
29
+ # TODO: Not sure how redis-rb is handling this but they're not raising an error
30
+ # it 'raises an error on subsecond timeouts' do
31
+ # lambda do
32
+ # @redises.brpop(@list1, @list2, :timeout => 0.5)
33
+ # end.should raise_error(Redis::CommandError)
34
+ # end
34
35
 
35
36
  it 'raises an error on negative timeout' do
36
37
  lambda do
@@ -1,6 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe '#del(key [, key, ...])' do
4
+ before :all do
5
+ sleep 1 - (Time.now.to_f % 1)
6
+ end
7
+
8
+ before :each do
9
+ @redises._gsub(/\d{3}-\d/, '...-.')
10
+ end
11
+
4
12
  it 'returns the number of keys deleted' do
5
13
  @redises.set('mock-redis-test:1', 1)
6
14
  @redises.set('mock-redis-test:2', 1)
@@ -32,4 +40,11 @@ describe '#del(key [, key, ...])' do
32
40
  it 'raises an error if an empty array is given' do
33
41
  expect { @redises.del [] }.to raise_error Redis::CommandError
34
42
  end
43
+
44
+ it 'removes a stream key' do
45
+ @redises.xadd('mock-redis-stream', { key: 'value' }, maxlen: 0)
46
+ expect(@redises.exists?('mock-redis-stream')).to eq true
47
+ @redises.del('mock-redis-stream')
48
+ expect(@redises.exists?('mock-redis-stream')).to eq false
49
+ end
35
50
  end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#dump(key)' do
4
+ before do
5
+ @key = 'mock-redis-test:45794'
6
+ # These are mock-only, since our dump/restore implementations
7
+ # aren't compatible with real redis.
8
+ @mock = @redises.mock
9
+ end
10
+
11
+ it 'returns nil for keys that do not exist' do
12
+ @mock.dump(@key).should be_nil
13
+ end
14
+
15
+ it 'returns a serialized value for keys that do exist' do
16
+ @mock.set(@key, '2')
17
+ @mock.dump(@key).should == Marshal.dump('2')
18
+ end
19
+ end
@@ -1,14 +1,43 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe '#exists(key)' do
4
- before { @key = 'mock-redis-test:45794' }
3
+ describe '#exists(*keys)' do
4
+ before { @key1 = 'mock-redis-test:exists1' }
5
+ before { @key2 = 'mock-redis-test:exists2' }
6
+
7
+ it 'returns 0 for keys that do not exist' do
8
+ @redises.exists(@key1).should == 0
9
+ @redises.exists(@key1, @key2).should == 0
10
+ end
11
+
12
+ it 'returns 1 for keys that do exist' do
13
+ @redises.set(@key1, 1)
14
+ @redises.exists(@key1).should == 1
15
+ end
16
+
17
+ it 'returns the count of all keys that exist' do
18
+ @redises.set(@key1, 1)
19
+ @redises.set(@key2, 1)
20
+ @redises.exists(@key1, @key2).should == 2
21
+ @redises.exists(@key1, @key2, 'does-not-exist').should == 2
22
+ end
23
+ end
24
+
25
+ describe '#exists?(*keys)' do
26
+ before { @key1 = 'mock-redis-test:exists1' }
27
+ before { @key2 = 'mock-redis-test:exists2' }
5
28
 
6
29
  it 'returns false for keys that do not exist' do
7
- @redises.exists(@key).should == false
30
+ @redises.exists?(@key1).should == false
31
+ @redises.exists?(@key1, @key2).should == false
8
32
  end
9
33
 
10
34
  it 'returns true for keys that do exist' do
11
- @redises.set(@key, 1)
12
- @redises.exists(@key).should == true
35
+ @redises.set(@key1, 1)
36
+ @redises.exists?(@key1).should == true
37
+ end
38
+
39
+ it 'returns true if any keys exist' do
40
+ @redises.set(@key2, 1)
41
+ @redises.exists?(@key1, @key2).should == true
13
42
  end
14
43
  end
@@ -3,7 +3,12 @@ require 'spec_helper'
3
3
  describe MockRedis::Future do
4
4
  let(:command) { [:get, 'foo'] }
5
5
  let(:result) { 'bar' }
6
- before { @future = MockRedis::Future.new(command) }
6
+ let(:block) { ->(value) { value.upcase } }
7
+
8
+ before do
9
+ @future = MockRedis::Future.new(command)
10
+ @future2 = MockRedis::Future.new(command, block)
11
+ end
7
12
 
8
13
  it 'remembers the command' do
9
14
  @future.command.should eq(command)
@@ -17,4 +22,9 @@ describe MockRedis::Future do
17
22
  @future.store_result(result)
18
23
  @future.value.should eq(result)
19
24
  end
25
+
26
+ it 'executes the block on the value if block is passed in' do
27
+ @future2.store_result(result)
28
+ @future2.value.should eq('BAR')
29
+ end
20
30
  end
@@ -33,7 +33,7 @@ describe '#geoadd' do
33
33
  context 'when coordinates are not in allowed range' do
34
34
  let(:coords) { [181, 86] }
35
35
  let(:message) do
36
- formatted_coords = coords.map { |c| format('%.6f', c) }
36
+ formatted_coords = coords.map { |c| format('%<coords>.6f', coords: c) }
37
37
  "ERR invalid longitude,latitude pair #{formatted_coords.join(',')}"
38
38
  end
39
39
 
@@ -50,5 +50,21 @@ describe '#hdel(key, field)' do
50
50
  @redises.hget(@key, field).should be_nil
51
51
  end
52
52
 
53
+ it 'supports a variable number of fields as array' do
54
+ @redises.hdel(@key, %w[k1 k2]).should == 2
55
+
56
+ @redises.hget(@key, 'k1').should be_nil
57
+ @redises.hget(@key, 'k2').should be_nil
58
+ @redises.get(@key).should be_nil
59
+ end
60
+
61
+ it 'supports a list of fields in various way' do
62
+ @redises.hdel(@key, ['k1'], 'k2').should == 2
63
+
64
+ @redises.hget(@key, 'k1').should be_nil
65
+ @redises.hget(@key, 'k2').should be_nil
66
+ @redises.get(@key).should be_nil
67
+ end
68
+
53
69
  it_should_behave_like 'a hash-only command'
54
70
  end
@@ -39,5 +39,31 @@ describe '#hmset(key, field, value [, field, value ...])' do
39
39
  end.should raise_error(Redis::CommandError)
40
40
  end
41
41
 
42
+ # The following tests address https://github.com/sds/mock_redis/issues/170
43
+ context 'keys are stored as strings' do
44
+ before do
45
+ @redises.hmset(1, :foo, :bar)
46
+ @redises.hmset(:a_sym, :boo, :bas)
47
+ end
48
+
49
+ it { expect(@redises.hgetall('1')).to eq @redises.hgetall(1) }
50
+ it { expect(@redises.hgetall('a_sym')).to eq @redises.hgetall(:a_sym) }
51
+ it { expect(@redises.del('1')).to eq 1 }
52
+ it { expect(@redises.del(1)).to eq 1 }
53
+ it { expect(@redises.del('a_sym')).to eq 1 }
54
+ it { expect(@redises.del(:a_sym)).to eq 1 }
55
+
56
+ after do
57
+ @redises.del(1)
58
+ @redises.del(:a_sym)
59
+ end
60
+ end
61
+
62
+ # The following tests address https://github.com/sds/mock_redis/issues/134
63
+ context 'hmset accepts an array as its only argument' do
64
+ it { expect(@redises.hmset([@key, :bar, :bas])).to eq 'OK' }
65
+ it { lambda { @redises.hmset([:foo, :bar]) }.should raise_error(Redis::CommandError) }
66
+ end
67
+
42
68
  it_should_behave_like 'a hash-only command'
43
69
  end
@@ -5,18 +5,18 @@ describe '#hset(key, field)' do
5
5
  @key = 'mock-redis-test:hset'
6
6
  end
7
7
 
8
- it 'returns true if the key does not exist' do
9
- @redises.hset(@key, 'k1', 'v1').should == true
8
+ it 'returns 1 if the key does not exist' do
9
+ @redises.hset(@key, 'k1', 'v1').should == 1
10
10
  end
11
11
 
12
- it 'returns true if the key exists but the field does not' do
12
+ it 'returns 1 if the key exists but the field does not' do
13
13
  @redises.hset(@key, 'k1', 'v1')
14
- @redises.hset(@key, 'k2', 'v2').should == true
14
+ @redises.hset(@key, 'k2', 'v2').should == 1
15
15
  end
16
16
 
17
- it 'returns false if the field already exists' do
17
+ it 'returns 0 if the field already exists' do
18
18
  @redises.hset(@key, 'k1', 'v1')
19
- @redises.hset(@key, 'k1', 'v1').should == false
19
+ @redises.hset(@key, 'k1', 'v1').should == 0
20
20
  end
21
21
 
22
22
  it 'creates a hash there is no such field' do
@@ -29,6 +29,7 @@ describe '#keys()' do
29
29
 
30
30
  @redises.set('mock-redis-test:special-key?', 'true')
31
31
  @redises.set('mock-redis-test:special-key*', 'true')
32
+ @redises.set('mock-redis-test:special-key-!?*', 'true')
32
33
  end
33
34
 
34
35
  describe 'the ? character' do
@@ -53,6 +54,22 @@ describe '#keys()' do
53
54
  'mock-redis-test:special-key?',
54
55
  ]
55
56
  end
57
+
58
+ context 'multiple ? characters' do
59
+ it "properly handles multiple consequtive '?' characters" do
60
+ @redises.keys('mock-redis-test:special-key-???').sort.should == [
61
+ 'mock-redis-test:special-key-!?*',
62
+ ]
63
+ end
64
+
65
+ context '\\? as a literal ' do
66
+ it 'handles multiple ? as both literal and special character' do
67
+ @redises.keys('mock-redis-test:special-key-?\??').sort.should == [
68
+ 'mock-redis-test:special-key-!?*',
69
+ ]
70
+ end
71
+ end
72
+ end
56
73
  end
57
74
 
58
75
  describe 'the * character' do
@@ -49,5 +49,11 @@ describe '#mget(key [, key, ...])' do
49
49
  @redises.mget
50
50
  end.should raise_error(Redis::CommandError)
51
51
  end
52
+
53
+ it 'raises an error if you pass it empty array' do
54
+ lambda do
55
+ @redises.mget([])
56
+ end.should raise_error(Redis::CommandError)
57
+ end
52
58
  end
53
59
  end
@@ -64,7 +64,7 @@ describe '#move(key, db)' do
64
64
  end
65
65
 
66
66
  it 'removes key from srcdb' do
67
- @redises.exists(@key).should == false
67
+ @redises.exists?(@key).should == false
68
68
  end
69
69
 
70
70
  it 'copies key to destdb' do
@@ -81,7 +81,7 @@ describe '#move(key, db)' do
81
81
  end
82
82
 
83
83
  it 'removes key from srcdb' do
84
- @redises.exists(@key).should == false
84
+ @redises.exists?(@key).should == false
85
85
  end
86
86
 
87
87
  it 'copies key to destdb' do
@@ -99,7 +99,7 @@ describe '#move(key, db)' do
99
99
  end
100
100
 
101
101
  it 'removes key from srcdb' do
102
- @redises.exists(@key).should == false
102
+ @redises.exists?(@key).should == false
103
103
  end
104
104
 
105
105
  it 'copies key to destdb' do
@@ -117,7 +117,7 @@ describe '#move(key, db)' do
117
117
  end
118
118
 
119
119
  it 'removes key from srcdb' do
120
- @redises.exists(@key).should == false
120
+ @redises.exists?(@key).should == false
121
121
  end
122
122
 
123
123
  it 'copies key to destdb' do
@@ -135,7 +135,7 @@ describe '#move(key, db)' do
135
135
  end
136
136
 
137
137
  it 'removes key from srcdb' do
138
- @redises.exists(@key).should == false
138
+ @redises.exists?(@key).should == false
139
139
  end
140
140
 
141
141
  it 'copies key to destdb' do
@@ -66,6 +66,26 @@ describe '#pipelined' do
66
66
  end
67
67
  end
68
68
 
69
+ context 'with redis time return value' do
70
+ let(:time_stub) { double 'Time', :now => Time.new(2019, 1, 2, 3, 4, 6, '+00:00') }
71
+ let(:options) { { :time_class => time_stub } }
72
+
73
+ subject { MockRedis.new(options) }
74
+
75
+ it 'returns the time value' do
76
+ subject.set('foo', 'bar')
77
+
78
+ results = subject.pipelined do
79
+ subject.get('foo')
80
+ subject.host # defined on MockRedis, so not captured
81
+ subject.time
82
+ subject.echo('baz')
83
+ end
84
+
85
+ expect(results).to eq(['bar', [1_546_398_246, 0], 'baz'])
86
+ end
87
+ end
88
+
69
89
  context 'with nested pipelines' do
70
90
  let(:key1) { 'hello' }
71
91
  let(:key2) { 'world' }
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#restore(key, ttl, value)' do
4
+ before do
5
+ @key = 'mock-redis-test:45794'
6
+ @src = MockRedis.new
7
+ @src.set(@key, '123')
8
+ @dumped_value = @src.dump(@key)
9
+ @dst = MockRedis.new
10
+ @now = Time.now.round
11
+ Time.stub(:now).and_return(@now)
12
+ end
13
+
14
+ it 'allows dump/restoring values between two redis instances' do
15
+ expect(@dst.restore(@key, 0, @dumped_value)).to eq('OK')
16
+ expect(@dst.get(@key)).to eq('123')
17
+ expect(@dst.pttl(@key)).to eq(-1)
18
+ end
19
+
20
+ context 'when the key being restored to already exists' do
21
+ before do
22
+ @dst.set(@key, '456')
23
+ end
24
+
25
+ it 'raises an error by default' do
26
+ expect { @dst.restore(@key, 0, @dumped_value) }.to raise_error(Redis::CommandError)
27
+ expect(@dst.get(@key)).to eq('456')
28
+ end
29
+
30
+ it 'allows replacing the key if replace==true' do
31
+ expect(@dst.restore(@key, 0, @dumped_value, replace: true)).to eq('OK')
32
+ expect(@dst.get(@key)).to eq('123')
33
+ end
34
+ end
35
+
36
+ it 'sets ttl in ms' do
37
+ @dst.restore(@key, 500, @dumped_value)
38
+ expect(@dst.pttl(@key)).to eq(500)
39
+ end
40
+
41
+ it 'can dump/restore more complex data types' do
42
+ key = 'a_hash'
43
+ @src.mapped_hmset(key, foo: 'bar')
44
+ @dst.restore(key, 0, @src.dump(key))
45
+ expect(@dst.hgetall(key)).to eq('foo' => 'bar')
46
+ end
47
+ end
@@ -42,6 +42,15 @@ describe '#scan' do
42
42
  end
43
43
  end
44
44
 
45
+ context 'when cursor is greater than collection size' do
46
+ let(:collection) { Array.new(count) { |i| "mock:key#{i}" } }
47
+ let(:expected) { ['0', []] }
48
+
49
+ it 'returns a 0 cursor and empty collection' do
50
+ expect(subject.scan(20, count: count, match: match)).to eq(expected)
51
+ end
52
+ end
53
+
45
54
  context 'when giving a custom match filter' do
46
55
  let(:match) { 'mock:key*' }
47
56
  let(:collection) { %w[mock:key mock:key2 mock:otherkey] }
@@ -33,6 +33,14 @@ describe '#set(key, value)' do
33
33
  @redises.set(key, 1, xx: true).should == true
34
34
  end
35
35
 
36
+ it 'raises on unknown options' do
37
+ key = 'mock-redis-test'
38
+ @redises.del(key)
39
+ expect do
40
+ @redises.set(key, 1, logger: :something)
41
+ end.to raise_error(ArgumentError, 'unknown keyword: logger')
42
+ end
43
+
36
44
  context '[mock only]' do
37
45
  before(:all) do
38
46
  @mock = @redises.mock
@@ -53,9 +61,11 @@ describe '#set(key, value)' do
53
61
 
54
62
  it 'accepts PX milliseconds' do
55
63
  key = 'mock-redis-test'
56
- @mock.set(key, 1, px: 1000).should == 'OK'
64
+ @mock.set(key, 1, px: 500).should == 'OK'
57
65
  @mock.get(key).should_not be_nil
58
- Time.stub(:now).and_return(@now + 2)
66
+ Time.stub(:now).and_return(@now + 300 / 1000.to_f)
67
+ @mock.get(key).should_not be_nil
68
+ Time.stub(:now).and_return(@now + 600 / 1000.to_f)
59
69
  @mock.get(key).should be_nil
60
70
  end
61
71
  end