mock_redis 0.20.0 → 0.25.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.
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