mock_redis 0.22.0 → 0.27.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -5
  3. data/.rubocop_todo.yml +1 -1
  4. data/.travis.yml +1 -0
  5. data/CHANGELOG.md +31 -0
  6. data/Gemfile +4 -2
  7. data/lib/mock_redis.rb +1 -8
  8. data/lib/mock_redis/connection_method.rb +13 -0
  9. data/lib/mock_redis/database.rb +44 -14
  10. data/lib/mock_redis/expire_wrapper.rb +1 -1
  11. data/lib/mock_redis/future.rb +1 -1
  12. data/lib/mock_redis/geospatial_methods.rb +5 -5
  13. data/lib/mock_redis/hash_methods.rb +9 -4
  14. data/lib/mock_redis/info_method.rb +2 -2
  15. data/lib/mock_redis/multi_db_wrapper.rb +3 -3
  16. data/lib/mock_redis/pipelined_wrapper.rb +1 -1
  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 +16 -1
  20. data/lib/mock_redis/string_methods.rb +27 -20
  21. data/lib/mock_redis/transaction_wrapper.rb +3 -3
  22. data/lib/mock_redis/utility_methods.rb +1 -1
  23. data/lib/mock_redis/version.rb +1 -1
  24. data/lib/mock_redis/zset_methods.rb +34 -9
  25. data/mock_redis.gemspec +1 -1
  26. data/spec/commands/blpop_spec.rb +0 -6
  27. data/spec/commands/brpop_spec.rb +6 -5
  28. data/spec/commands/connection_spec.rb +15 -0
  29. data/spec/commands/del_spec.rb +17 -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/hset_spec.rb +6 -6
  35. data/spec/commands/keys_spec.rb +17 -0
  36. data/spec/commands/mget_spec.rb +6 -0
  37. data/spec/commands/move_spec.rb +5 -5
  38. data/spec/commands/pipelined_spec.rb +20 -0
  39. data/spec/commands/restore_spec.rb +47 -0
  40. data/spec/commands/set_spec.rb +59 -9
  41. data/spec/commands/setbit_spec.rb +1 -0
  42. data/spec/commands/setex_spec.rb +16 -0
  43. data/spec/commands/srandmember_spec.rb +1 -1
  44. data/spec/commands/xadd_spec.rb +23 -3
  45. data/spec/commands/xlen_spec.rb +3 -1
  46. data/spec/commands/xrange_spec.rb +13 -0
  47. data/spec/commands/xread_spec.rb +66 -0
  48. data/spec/commands/xtrim_spec.rb +6 -0
  49. data/spec/commands/zinterstore_spec.rb +34 -0
  50. data/spec/commands/zrange_spec.rb +1 -1
  51. data/spec/commands/zrangebyscore_spec.rb +1 -1
  52. data/spec/commands/zrevrange_spec.rb +1 -1
  53. data/spec/commands/zrevrangebyscore_spec.rb +1 -1
  54. data/spec/commands/zunionstore_spec.rb +33 -0
  55. data/spec/spec_helper.rb +2 -1
  56. data/spec/support/redis_multiplexer.rb +2 -1
  57. data/spec/transactions_spec.rb +16 -0
  58. metadata +14 -5
@@ -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
 
@@ -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
@@ -1,6 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe '#set(key, value)' do
4
+ let(:key) { 'mock-redis-test' }
5
+
4
6
  it "responds with 'OK'" do
5
7
  @redises.set('mock-redis-test', 1).should == 'OK'
6
8
  end
@@ -19,24 +21,72 @@ describe '#set(key, value)' do
19
21
  end
20
22
 
21
23
  it 'accepts NX' do
22
- key = 'mock-redis-test'
23
24
  @redises.del(key)
24
25
  @redises.set(key, 1, nx: true).should == true
25
26
  @redises.set(key, 1, nx: true).should == false
26
27
  end
27
28
 
28
29
  it 'accepts XX' do
29
- key = 'mock-redis-test'
30
30
  @redises.del(key)
31
31
  @redises.set(key, 1, xx: true).should == false
32
32
  @redises.set(key, 1).should == 'OK'
33
33
  @redises.set(key, 1, xx: true).should == true
34
34
  end
35
35
 
36
- it 'ignores other options' do
37
- key = 'mock-redis-test'
36
+ it 'sets the ttl to -1' do
37
+ @redises.set(key, 1)
38
+ expect(@redises.ttl(key)).to eq(-1)
39
+ end
40
+
41
+ context 'with an expiry time' do
42
+ before :each do
43
+ Timecop.freeze
44
+ @redises.set(key, 1, ex: 90)
45
+ end
46
+
47
+ after :each do
48
+ @redises.del(key)
49
+ Timecop.return
50
+ end
51
+
52
+ it 'has the TTL set' do
53
+ expect(@redises.ttl(key)).to eq 90
54
+ end
55
+
56
+ it 'resets the TTL without keepttl' do
57
+ expect do
58
+ @redises.set(key, 2)
59
+ end.to change { @redises.ttl(key) }.from(90).to(-1)
60
+ end
61
+
62
+ it 'does not change the TTL with keepttl: true' do
63
+ expect do
64
+ @redises.set(key, 2, keepttl: true)
65
+ end.not_to change { @redises.ttl(key) }.from(90)
66
+ end
67
+ end
68
+
69
+ it 'accepts KEEPTTL' do
70
+ expect(@redises.set(key, 1, keepttl: true)).to eq 'OK'
71
+ end
72
+
73
+ it 'does not set TTL without ex' do
74
+ @redises.set(key, 1)
75
+ expect(@redises.ttl(key)).to eq(-1)
76
+ end
77
+
78
+ it 'sets the TTL' do
79
+ Timecop.freeze do
80
+ @redises.set(key, 1, ex: 90)
81
+ expect(@redises.ttl(key)).to eq 90
82
+ end
83
+ end
84
+
85
+ it 'raises on unknown options' do
38
86
  @redises.del(key)
39
- @redises.set(key, 1, logger: :something).should == 'OK'
87
+ expect do
88
+ @redises.set(key, 1, logger: :something)
89
+ end.to raise_error(ArgumentError, /unknown keyword/)
40
90
  end
41
91
 
42
92
  context '[mock only]' do
@@ -50,7 +100,6 @@ describe '#set(key, value)' do
50
100
  end
51
101
 
52
102
  it 'accepts EX seconds' do
53
- key = 'mock-redis-test'
54
103
  @mock.set(key, 1, ex: 1).should == 'OK'
55
104
  @mock.get(key).should_not be_nil
56
105
  Time.stub(:now).and_return(@now + 2)
@@ -58,10 +107,11 @@ describe '#set(key, value)' do
58
107
  end
59
108
 
60
109
  it 'accepts PX milliseconds' do
61
- key = 'mock-redis-test'
62
- @mock.set(key, 1, px: 1000).should == 'OK'
110
+ @mock.set(key, 1, px: 500).should == 'OK'
63
111
  @mock.get(key).should_not be_nil
64
- Time.stub(:now).and_return(@now + 2)
112
+ Time.stub(:now).and_return(@now + 300 / 1000.to_f)
113
+ @mock.get(key).should_not be_nil
114
+ Time.stub(:now).and_return(@now + 600 / 1000.to_f)
65
115
  @mock.get(key).should be_nil
66
116
  end
67
117
  end
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe '#setbit(key, offset)' do
4
4
  before do
5
+ Encoding.default_external = 'UTF-8'
5
6
  @key = 'mock-redis-test:setbit'
6
7
  @redises.set(@key, 'h') # ASCII 0x68
7
8
  end
@@ -19,4 +19,20 @@ describe '#setex(key, seconds, value)' do
19
19
  @redises.real.ttl(@key).should > 0
20
20
  @redises.mock.ttl(@key).should > 0
21
21
  end
22
+
23
+ context 'when expiration time is zero' do
24
+ it 'raises Redis::CommandError' do
25
+ expect do
26
+ @redises.setex(@key, 0, 'value')
27
+ end.to raise_error(Redis::CommandError, 'ERR invalid expire time in setex')
28
+ end
29
+ end
30
+
31
+ context 'when expiration time is negative' do
32
+ it 'raises Redis::CommandError' do
33
+ expect do
34
+ @redises.setex(@key, -2, 'value')
35
+ end.to raise_error(Redis::CommandError, 'ERR invalid expire time in setex')
36
+ end
37
+ end
22
38
  end
@@ -37,7 +37,7 @@ describe '#srandmember(key)' do
37
37
  @redises.send_without_checking(:srandmember, @key, 2).size.should == 2
38
38
  end
39
39
 
40
- it 'returns random members up to count from the set when count is negative even if count.abs is greater than the set size' do # rubocop:disable Metrics/LineLength
40
+ it 'returns random members up to count from the set when count is negative even if count.abs is greater than the set size' do # rubocop:disable Layout/LineLength
41
41
  @redises.send_without_checking(:srandmember, @key, -5).size.should == 5
42
42
  end
43
43
 
@@ -7,11 +7,14 @@ describe '#xadd("mystream", { f1: "v1", f2: "v2" }, id: "0-0", maxlen: 1000, app
7
7
  end
8
8
 
9
9
  before :each do
10
- @redises._gsub(/\d{3}-\d/, '...-.')
10
+ # TODO: Redis appears to be returning a timestamp a few seconds in the future
11
+ # so we're ignoring the last 5 digits (time in milliseconds)
12
+ @redises._gsub(/\d{5}-\d/, '....-.')
11
13
  end
12
14
 
13
15
  it 'returns an id based on the timestamp' do
14
16
  t = Time.now.to_i
17
+ id = @redises.xadd(@key, key: 'value')
15
18
  expect(@redises.xadd(@key, key: 'value')).to match(/#{t}\d{3}-0/)
16
19
  end
17
20
 
@@ -52,8 +55,7 @@ describe '#xadd("mystream", { f1: "v1", f2: "v2" }, id: "0-0", maxlen: 1000, app
52
55
  expect { @redises.xadd('mock-redis-test:unknown-stream', { key: 'value' }, id: '0') }
53
56
  .to raise_error(
54
57
  Redis::CommandError,
55
- 'ERR The ID specified in XADD is equal or smaller than the target ' \
56
- 'stream top item'
58
+ 'ERR The ID specified in XADD must be greater than 0-0'
57
59
  )
58
60
  end
59
61
 
@@ -99,4 +101,22 @@ describe '#xadd("mystream", { f1: "v1", f2: "v2" }, id: "0-0", maxlen: 1000, app
99
101
  ]
100
102
  )
101
103
  end
104
+
105
+ it 'supports a maxlen greater than the current size' do
106
+ @redises.xadd(@key, { key1: 'value1' }, id: '1234567891234-0')
107
+ @redises.xadd(@key, { key2: 'value2' }, id: '1234567891245-0', maxlen: 1000)
108
+ expect(@redises.xrange(@key, '-', '+')).to eq(
109
+ [
110
+ ['1234567891234-0', { 'key1' => 'value1' }],
111
+ ['1234567891245-0', { 'key2' => 'value2' }],
112
+ ]
113
+ )
114
+ end
115
+
116
+ it 'creates an empty stream with maxlen of 0' do
117
+ @redises.xadd(@key, { key: 'value' }, maxlen: 0)
118
+ expect(@redises.xlen(@key)).to eq 0
119
+ expect(@redises.xrange(@key, '-', '+')).to eq([])
120
+ expect(@redises.exists?(@key)).to eq true
121
+ end
102
122
  end
@@ -7,7 +7,9 @@ describe '#xlen(key)' do
7
7
  end
8
8
 
9
9
  before :each do
10
- @redises._gsub(/\d{3}-\d/, '...-.')
10
+ # TODO: Redis appears to be returning a timestamp a few seconds in the future
11
+ # so we're ignoring the last 5 digits (time in milliseconds)
12
+ @redises._gsub(/\d{5}-\d/, '...-.')
11
13
  end
12
14
 
13
15
  it 'returns the number of items in the stream' do
@@ -54,6 +54,19 @@ describe '#xrange("mystream", first: "0-1", last: "0-3", count: 10)' do
54
54
  )
55
55
  end
56
56
 
57
+ it 'returns all entries with a lower limit of 0-0' do
58
+ expect(@redises.xrange(@key, '0-0', '+')).to eq(
59
+ [
60
+ ['1234567891234-0', { 'key1' => 'value1' }],
61
+ ['1234567891245-0', { 'key2' => 'value2' }],
62
+ ['1234567891245-1', { 'key3' => 'value3' }],
63
+ ['1234567891278-0', { 'key4' => 'value4' }],
64
+ ['1234567891278-1', { 'key5' => 'value5' }],
65
+ ['1234567891299-0', { 'key6' => 'value6' }]
66
+ ]
67
+ )
68
+ end
69
+
57
70
  it 'returns entries with an upper limit' do
58
71
  expect(@redises.xrange(@key, '-', '1234567891285-0')).to eq(
59
72
  [