mock_redis 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d1b68c8d02b25dcd30e463797ca26ad87142cbba1e85f43ef635e59adc1b6f2
4
- data.tar.gz: 7daf9875e33a704d73917a83d71878ea931671c5a34d4382b3b8f888b98c18ff
3
+ metadata.gz: 5bbc0edb10b18ba89981fe78e35e169dbbdc4af0886e01f07e0947f8eb7eff45
4
+ data.tar.gz: 88d094d92b9c55ebc677dca8c2a6561805b79dbf11d97b2992a9c1312cccc1b1
5
5
  SHA512:
6
- metadata.gz: 60b2f391b1ce3e8bce7986bdc19918c5ef5f8453bcea9ffc0ced69e61fd8233ade6c93a9efa71aff4ec7f8b348b8737a1347bb2494f7d5f6587c32a2c4698a18
7
- data.tar.gz: 9dd87f2dfae5a2fd7e97cbc3f8396b70f5cc6f30d29d1c9f3b5849b60b7180d02a32a17e8686f9cdc716cbb44d04ae411839aaa518d4da70cc07cca9b1893f66
6
+ metadata.gz: c805d6ad39374b422b8f0fd571ceae353e91531089c1844459dd046bbaa97b1cc8d80a6dec386f3c5f93382c39081084c799f1ad286d37e5a1d0e1c0a3f55279
7
+ data.tar.gz: cfc5b689845fccd69eb744a1bc8296530ce46c3686cf15cca272bf12bad8520f12739289cc4b21e99f11762127fb74a72eda54ca19ed7e27afe90fe4d26545db
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.47.0
4
+
5
+ * Add support for `redis-rb` gem versions 5.x
6
+ * Drop support for `redis-rb` gem versions < 5.x
7
+
8
+ ### 0.46.0
9
+
10
+ * Fix `hset` for array of key-value pairs
11
+
3
12
  ### 0.45.0
4
13
 
5
14
  * Add support for `count` argument of `rpop`
data/README.md CHANGED
@@ -10,9 +10,10 @@ for use in tests.
10
10
 
11
11
  ## Requirements
12
12
 
13
- Ruby 3.0+
13
+ Ruby 3.x
14
+ redis-rb 5.x
14
15
 
15
- The current implementation is tested against Redis 6.2. Older versions may work, but can also return different results or not support some commands.
16
+ The current implementation is **tested against Redis 6.2 and 7.0**. Older versions may work, but can also return different results or not support some commands.
16
17
 
17
18
  ## Getting Started
18
19
 
@@ -1,11 +1,27 @@
1
+ require 'mock_redis/error'
2
+
1
3
  class MockRedis
4
+ DUMP_TYPES = RedisClient::RESP3::DUMP_TYPES
5
+
2
6
  module Assertions
3
7
  private
4
8
 
5
9
  def assert_has_args(args, command)
6
- unless args.any?
7
- raise Redis::CommandError,
8
- "ERR wrong number of arguments for '#{command}' command"
10
+ if args.empty?
11
+ raise Error.command_error(
12
+ "ERR wrong number of arguments for '#{command}' command",
13
+ self
14
+ )
15
+ end
16
+ end
17
+
18
+ def assert_type(*args)
19
+ args.each do |arg|
20
+ DUMP_TYPES.fetch(arg.class) do |unexpected_class|
21
+ unless DUMP_TYPES.keys.find { |t| t > unexpected_class }
22
+ raise TypeError, "Unsupported command argument type: #{unexpected_class}"
23
+ end
24
+ end
9
25
  end
10
26
  end
11
27
  end
@@ -95,13 +95,13 @@ class MockRedis
95
95
  end
96
96
 
97
97
  def expire(key, seconds, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
98
- assert_valid_integer(seconds)
98
+ seconds = Integer(seconds)
99
99
 
100
100
  pexpire(key, seconds.to_i * 1000, nx: nx, xx: xx, lt: lt, gt: gt)
101
101
  end
102
102
 
103
103
  def pexpire(key, ms, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
104
- assert_valid_integer(ms)
104
+ ms = Integer(ms)
105
105
 
106
106
  now, miliseconds = @base.now
107
107
  now_ms = (now * 1000) + miliseconds
@@ -109,18 +109,19 @@ class MockRedis
109
109
  end
110
110
 
111
111
  def expireat(key, timestamp, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
112
- assert_valid_integer(timestamp)
112
+ timestamp = Integer(timestamp)
113
113
 
114
114
  pexpireat(key, timestamp.to_i * 1000, nx: nx, xx: xx, lt: lt, gt: gt)
115
115
  end
116
116
 
117
117
  def pexpireat(key, timestamp_ms, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
118
- assert_valid_integer(timestamp_ms)
118
+ timestamp_ms = Integer(timestamp_ms)
119
119
 
120
120
  if nx && gt || gt && lt || lt && nx || nx && xx
121
- raise Redis::CommandError, <<~TXT.chomp
122
- ERR NX and XX, GT or LT options at the same time are not compatible
123
- TXT
121
+ raise Error.command_error(
122
+ 'ERR NX and XX, GT or LT options at the same time are not compatible',
123
+ self
124
+ )
124
125
  end
125
126
 
126
127
  return false unless exists?(key)
@@ -157,7 +158,7 @@ class MockRedis
157
158
 
158
159
  def restore(key, ttl, value, replace: false)
159
160
  if !replace && exists?(key)
160
- raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
161
+ raise Error.command_error('BUSYKEY Target key name already exists.', self)
161
162
  end
162
163
  data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
163
164
  if ttl > 0
@@ -211,7 +212,7 @@ class MockRedis
211
212
 
212
213
  def rename(key, newkey)
213
214
  unless data.include?(key)
214
- raise Redis::CommandError, 'ERR no such key'
215
+ raise Error.command_error('ERR no such key', self)
215
216
  end
216
217
 
217
218
  if key != newkey
@@ -227,7 +228,7 @@ class MockRedis
227
228
 
228
229
  def renamenx(key, newkey)
229
230
  unless data.include?(key)
230
- raise Redis::CommandError, 'ERR no such key'
231
+ raise Error.command_error('ERR no such key', self)
231
232
  end
232
233
 
233
234
  if exists?(newkey)
@@ -301,19 +302,13 @@ class MockRedis
301
302
 
302
303
  private
303
304
 
304
- def assert_valid_integer(integer)
305
- unless looks_like_integer?(integer.to_s)
306
- raise Redis::CommandError, 'ERR value is not an integer or out of range'
307
- end
308
- integer
309
- end
310
-
311
305
  def assert_valid_timeout(timeout)
312
- if !looks_like_integer?(timeout.to_s)
313
- raise Redis::CommandError, 'ERR timeout is not an integer or out of range'
314
- elsif timeout < 0
315
- raise Redis::CommandError, 'ERR timeout is negative'
306
+ timeout = Integer(timeout)
307
+
308
+ if timeout < 0
309
+ raise ArgumentError, 'time interval must not be negative'
316
310
  end
311
+
317
312
  timeout
318
313
  end
319
314
 
@@ -347,7 +342,7 @@ class MockRedis
347
342
  end
348
343
 
349
344
  def looks_like_integer?(str)
350
- str =~ /^-?\d+$/
345
+ !!Integer(str) rescue false
351
346
  end
352
347
 
353
348
  def looks_like_float?(str)
@@ -0,0 +1,27 @@
1
+ class MockRedis
2
+ module Error
3
+ module_function
4
+
5
+ def build(error_class, message, database)
6
+ connection = database.connection
7
+ url = "redis://#{connection[:host]}:#{connection[:port]}"
8
+ error_class.new("#{message} (#{url})")
9
+ end
10
+
11
+ def wrong_type_error(database)
12
+ build(
13
+ Redis::WrongTypeError,
14
+ 'WRONGTYPE Operation against a key holding the wrong kind of value',
15
+ database
16
+ )
17
+ end
18
+
19
+ def syntax_error(database)
20
+ command_error('ERR syntax error', database)
21
+ end
22
+
23
+ def command_error(message, database)
24
+ build(Redis::CommandError, message, database)
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,5 @@
1
+ require 'mock_redis/error'
2
+
1
3
  class MockRedis
2
4
  module GeospatialMethods
3
5
  LNG_RANGE = (-180..180)
@@ -81,8 +83,7 @@ class MockRedis
81
83
  points = args.each_slice(3).to_a
82
84
 
83
85
  if points.last.size != 3
84
- raise Redis::CommandError,
85
- "ERR wrong number of arguments for 'geoadd' command"
86
+ raise Error.command_error("ERR wrong number of arguments for 'geoadd' command", self)
86
87
  end
87
88
 
88
89
  points.map do |point|
@@ -97,13 +98,15 @@ class MockRedis
97
98
  unless LNG_RANGE.include?(lng) && LAT_RANGE.include?(lat)
98
99
  lng = format('%<long>.6f', long: lng)
99
100
  lat = format('%<lat>.6f', lat: lat)
100
- raise Redis::CommandError,
101
- "ERR invalid longitude,latitude pair #{lng},#{lat}"
101
+ raise Error.command_error(
102
+ "ERR invalid longitude,latitude pair #{lng},#{lat}",
103
+ self
104
+ )
102
105
  end
103
106
 
104
107
  { key: point[2], lng: lng, lat: lat }
105
108
  rescue ArgumentError
106
- raise Redis::CommandError, 'ERR value is not a valid float'
109
+ raise Error.command_error('ERR value is not a valid float', self)
107
110
  end
108
111
 
109
112
  # Returns ZSET score for passed coordinates
@@ -212,8 +215,7 @@ class MockRedis
212
215
  unit = unit.to_sym
213
216
  return UNITS[unit] if UNITS[unit]
214
217
 
215
- raise Redis::CommandError,
216
- 'ERR unsupported unit provided. please use m, km, ft, mi'
218
+ raise Error.command_error('ERR unsupported unit provided. please use m, km, ft, mi', self)
217
219
  end
218
220
 
219
221
  def geohash_distance(lng1d, lat1d, lng2d, lat2d)
@@ -1,5 +1,6 @@
1
1
  require 'mock_redis/assertions'
2
2
  require 'mock_redis/utility_methods'
3
+ require 'mock_redis/error'
3
4
 
4
5
  class MockRedis
5
6
  module HashMethods
@@ -7,12 +8,16 @@ class MockRedis
7
8
  include UtilityMethods
8
9
 
9
10
  def hdel(key, *fields)
11
+ assert_type(key)
12
+
10
13
  with_hash_at(key) do |hash|
11
14
  orig_size = hash.size
12
15
  fields = Array(fields).flatten.map(&:to_s)
13
16
 
17
+ assert_type(*fields)
18
+
14
19
  if fields.empty?
15
- raise Redis::CommandError, "ERR wrong number of arguments for 'hdel' command"
20
+ raise Error.command_error("ERR wrong number of arguments for 'hdel' command", self)
16
21
  end
17
22
 
18
23
  hash.delete_if { |k, _v| fields.include?(k) }
@@ -21,25 +26,31 @@ class MockRedis
21
26
  end
22
27
 
23
28
  def hexists(key, field)
29
+ assert_type(key, field)
30
+
24
31
  with_hash_at(key) { |h| h.key?(field.to_s) }
25
32
  end
26
33
 
27
34
  def hget(key, field)
35
+ assert_type(key, field)
36
+
28
37
  with_hash_at(key) { |h| h[field.to_s] }
29
38
  end
30
39
 
31
40
  def hgetall(key)
41
+ assert_type(key)
42
+
32
43
  with_hash_at(key) { |h| h }
33
44
  end
34
45
 
35
46
  def hincrby(key, field, increment)
47
+ assert_type(key, field)
48
+
36
49
  with_hash_at(key) do |hash|
37
50
  field = field.to_s
51
+ increment = Integer(increment)
38
52
  unless can_incr?(data[key][field])
39
- raise Redis::CommandError, 'ERR hash value is not an integer'
40
- end
41
- unless looks_like_integer?(increment.to_s)
42
- raise Redis::CommandError, 'ERR value is not an integer or out of range'
53
+ raise Error.command_error('ERR hash value is not an integer', self)
43
54
  end
44
55
 
45
56
  new_value = (hash[field] || '0').to_i + increment.to_i
@@ -49,14 +60,13 @@ class MockRedis
49
60
  end
50
61
 
51
62
  def hincrbyfloat(key, field, increment)
63
+ assert_type(key, field)
64
+
52
65
  with_hash_at(key) do |hash|
53
66
  field = field.to_s
67
+ increment = Float(increment)
54
68
  unless can_incr_float?(data[key][field])
55
- raise Redis::CommandError, 'ERR hash value is not a float'
56
- end
57
-
58
- unless looks_like_float?(increment.to_s)
59
- raise Redis::CommandError, 'ERR value is not a valid float'
69
+ raise Error.command_error('ERR hash value is not a float', self)
60
70
  end
61
71
 
62
72
  new_value = (hash[field] || '0').to_f + increment.to_f
@@ -67,21 +77,28 @@ class MockRedis
67
77
  end
68
78
 
69
79
  def hkeys(key)
80
+ assert_type(key)
81
+
70
82
  with_hash_at(key, &:keys)
71
83
  end
72
84
 
73
85
  def hlen(key)
86
+ assert_type(key)
87
+
74
88
  hkeys(key).length
75
89
  end
76
90
 
77
91
  def hmget(key, *fields)
78
92
  fields.flatten!
79
93
 
94
+ assert_type(key, *fields)
80
95
  assert_has_args(fields, 'hmget')
81
96
  fields.map { |f| hget(key, f) }
82
97
  end
83
98
 
84
99
  def mapped_hmget(key, *fields)
100
+ fields.flatten!
101
+
85
102
  reply = hmget(key, *fields)
86
103
  if reply.is_a?(Array)
87
104
  Hash[fields.zip(reply)]
@@ -98,10 +115,15 @@ class MockRedis
98
115
  end
99
116
 
100
117
  kvpairs.flatten!
118
+
119
+ assert_type(key, *kvpairs)
101
120
  assert_has_args(kvpairs, 'hmset')
102
121
 
103
122
  if kvpairs.length.odd?
104
- raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for \'hmset\' command'
123
+ raise Error.command_error(
124
+ err_msg || "ERR wrong number of arguments for 'hmset' command",
125
+ self
126
+ )
105
127
  end
106
128
 
107
129
  kvpairs.each_slice(2) do |(k, v)|
@@ -111,21 +133,27 @@ class MockRedis
111
133
  end
112
134
 
113
135
  def mapped_hmset(key, hash)
114
- kvpairs = hash.to_a.flatten
136
+ kvpairs = hash.flatten
137
+
138
+ assert_type(key, *kvpairs)
115
139
  assert_has_args(kvpairs, 'hmset')
116
140
  if kvpairs.length.odd?
117
- raise Redis::CommandError, "ERR wrong number of arguments for 'hmset' command"
141
+ raise Error.command_error("ERR wrong number of arguments for 'hmset' command", self)
118
142
  end
119
143
 
120
144
  hmset(key, *kvpairs)
121
145
  end
122
146
 
123
147
  def hscan(key, cursor, opts = {})
148
+ assert_type(key, cursor)
149
+
124
150
  opts = opts.merge(key: lambda { |x| x[0] })
125
151
  common_scan(hgetall(key).to_a, cursor, opts)
126
152
  end
127
153
 
128
154
  def hscan_each(key, opts = {}, &block)
155
+ assert_type(key)
156
+
129
157
  return to_enum(:hscan_each, key, opts) unless block_given?
130
158
  cursor = 0
131
159
  loop do
@@ -137,11 +165,16 @@ class MockRedis
137
165
 
138
166
  def hset(key, *args)
139
167
  added = 0
168
+ args.flatten!(1)
169
+ assert_type(key)
170
+
140
171
  with_hash_at(key) do |hash|
141
172
  if args.length == 1 && args[0].is_a?(Hash)
142
173
  args = args[0].to_a.flatten
143
174
  end
144
175
 
176
+ assert_type(*args)
177
+
145
178
  args.each_slice(2) do |field, value|
146
179
  added += 1 unless hash.key?(field.to_s)
147
180
  hash[field.to_s] = value.to_s
@@ -151,6 +184,7 @@ class MockRedis
151
184
  end
152
185
 
153
186
  def hsetnx(key, field, value)
187
+ assert_type(key, field, value)
154
188
  if hget(key, field)
155
189
  false
156
190
  else
@@ -160,6 +194,8 @@ class MockRedis
160
194
  end
161
195
 
162
196
  def hvals(key)
197
+ assert_type(key)
198
+
163
199
  with_hash_at(key, &:values)
164
200
  end
165
201
 
@@ -175,8 +211,7 @@ class MockRedis
175
211
 
176
212
  def assert_hashy(key)
177
213
  unless hashy?(key)
178
- raise Redis::CommandError,
179
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
214
+ raise Error.wrong_type_error(self)
180
215
  end
181
216
  end
182
217
  end
@@ -46,9 +46,7 @@ class MockRedis
46
46
  end
47
47
  end
48
48
 
49
- def brpoplpush(source, destination, options = {})
50
- options = { :timeout => options } if options.is_a?(Integer)
51
- timeout = options.is_a?(Hash) && options[:timeout] || 0
49
+ def brpoplpush(source, destination, timeout: 0)
52
50
  assert_valid_timeout(timeout)
53
51
 
54
52
  if llen(source) > 0
@@ -66,7 +64,7 @@ class MockRedis
66
64
 
67
65
  def linsert(key, position, pivot, value)
68
66
  unless %w[before after].include?(position.to_s)
69
- raise Redis::CommandError, 'ERR syntax error'
67
+ raise Error.command_error('ERR syntax error', self)
70
68
  end
71
69
 
72
70
  assert_listy(key)
@@ -99,9 +97,8 @@ class MockRedis
99
97
  wherefrom = wherefrom.to_s.downcase
100
98
  whereto = whereto.to_s.downcase
101
99
 
102
- unless %w[left right].include?(wherefrom) && %w[left right].include?(whereto)
103
- raise Redis::CommandError, 'ERR syntax error'
104
- end
100
+ assert_where_field(wherefrom, 'where_source')
101
+ assert_where_field(whereto, 'where_destination')
105
102
 
106
103
  value = wherefrom == 'left' ? lpop(source) : rpop(source)
107
104
  (whereto == 'left' ? lpush(destination, value) : rpush(destination, value)) unless value.nil?
@@ -127,7 +124,7 @@ class MockRedis
127
124
  def lpushx(key, value)
128
125
  value = [value] unless value.is_a?(Array)
129
126
  if value.empty?
130
- raise Redis::CommandError, "ERR wrong number of arguments for 'lpushx' command"
127
+ raise Error.command_error("ERR wrong number of arguments for 'lpushx' command", self)
131
128
  end
132
129
  assert_listy(key)
133
130
  return 0 unless list_at?(key)
@@ -140,10 +137,7 @@ class MockRedis
140
137
  end
141
138
 
142
139
  def lrem(key, count, value)
143
- unless looks_like_integer?(count.to_s)
144
- raise Redis::CommandError, 'ERR value is not an integer or out of range'
145
- end
146
- count = count.to_i
140
+ count = Integer(count)
147
141
  value = value.to_s
148
142
 
149
143
  with_list_at(key) do |list|
@@ -167,12 +161,12 @@ class MockRedis
167
161
  assert_listy(key)
168
162
 
169
163
  unless list_at?(key)
170
- raise Redis::CommandError, 'ERR no such key'
164
+ raise Error.command_error('ERR no such key', self)
171
165
  end
172
166
 
173
167
  index = index.to_i
174
168
  unless (0...llen(key)).cover?(index)
175
- raise Redis::CommandError, 'ERR index out of range'
169
+ raise Error.command_error('ERR index out of range', self)
176
170
  end
177
171
 
178
172
  data[key][index] = value.to_s
@@ -211,7 +205,7 @@ class MockRedis
211
205
  def rpushx(key, value)
212
206
  value = [value] unless value.is_a?(Array)
213
207
  if value.empty?
214
- raise Redis::CommandError, "ERR wrong number of arguments for 'rpushx' command"
208
+ raise Error.command_error("ERR wrong number of arguments for 'rpushx' command", self)
215
209
  end
216
210
  assert_listy(key)
217
211
  return 0 unless list_at?(key)
@@ -235,8 +229,13 @@ class MockRedis
235
229
  def assert_listy(key)
236
230
  unless listy?(key)
237
231
  # Not the most helpful error, but it's what redis-rb barfs up
238
- raise Redis::CommandError,
239
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
232
+ raise Error.wrong_type_error(self)
233
+ end
234
+ end
235
+
236
+ def assert_where_field(where, argument_name)
237
+ unless %w[left right].include?(where)
238
+ raise ArgumentError, "#{argument_name} must be 'LEFT' or 'RIGHT'"
240
239
  end
241
240
  end
242
241
 
@@ -6,9 +6,10 @@ class MockRedis
6
6
  include Assertions
7
7
  include UtilityMethods
8
8
 
9
- def sadd(key, members)
9
+ def sadd(key, *members)
10
10
  members_class = members.class
11
- members = Array(members).map(&:to_s)
11
+ members = Array(members).flatten.map(&:to_s)
12
+ assert_type(key, *members)
12
13
  assert_has_args(members, 'sadd')
13
14
 
14
15
  with_set_at(key) do |s|
@@ -21,7 +22,7 @@ class MockRedis
21
22
  if members_class == Array
22
23
  s.size - size_before
23
24
  else
24
- added
25
+ added ? 1 : 0
25
26
  end
26
27
  end
27
28
  end
@@ -33,6 +34,7 @@ class MockRedis
33
34
  end
34
35
 
35
36
  def scard(key)
37
+ assert_type(key)
36
38
  with_set_at(key, &:length)
37
39
  end
38
40
 
@@ -42,6 +44,7 @@ class MockRedis
42
44
  end
43
45
 
44
46
  def sdiffstore(destination, *keys)
47
+ assert_type(destination, *keys)
45
48
  assert_has_args(keys, 'sdiffstore')
46
49
  with_set_at(destination) do |set|
47
50
  set.replace(sdiff(*keys))
@@ -58,6 +61,7 @@ class MockRedis
58
61
  end
59
62
 
60
63
  def sinterstore(destination, *keys)
64
+ assert_type(destination, *keys)
61
65
  assert_has_args(keys, 'sinterstore')
62
66
  with_set_at(destination) do |set|
63
67
  set.replace(sinter(*keys))
@@ -66,16 +70,21 @@ class MockRedis
66
70
  end
67
71
 
68
72
  def sismember(key, member)
73
+ assert_type(key, member)
69
74
  with_set_at(key) { |s| s.include?(member.to_s) }
70
75
  end
71
76
 
72
77
  def smismember(key, *members)
78
+ members.flatten!
79
+
80
+ assert_type(key, *members)
73
81
  with_set_at(key) do |set|
74
- members.flatten.map { |m| set.include?(m.to_s) }
82
+ members.map { |m| set.include?(m.to_s) }
75
83
  end
76
84
  end
77
85
 
78
86
  def smembers(key)
87
+ assert_type(key)
79
88
  with_set_at(key, &:to_a).map(&:dup).reverse
80
89
  end
81
90
 
@@ -93,6 +102,7 @@ class MockRedis
93
102
  end
94
103
 
95
104
  def spop(key, count = nil)
105
+ assert_type(key)
96
106
  with_set_at(key) do |set|
97
107
  if count.nil?
98
108
  member = set.first
@@ -112,6 +122,7 @@ class MockRedis
112
122
  end
113
123
 
114
124
  def srandmember(key, count = nil)
125
+ assert_type(key)
115
126
  members = with_set_at(key, &:to_a)
116
127
  if count
117
128
  if count > 0
@@ -124,7 +135,10 @@ class MockRedis
124
135
  end
125
136
  end
126
137
 
127
- def srem(key, members)
138
+ def srem(key, *members)
139
+ members = members.flatten.uniq
140
+ assert_type(key, *members)
141
+
128
142
  with_set_at(key) do |s|
129
143
  if members.is_a?(Array)
130
144
  orig_size = s.size
@@ -177,6 +191,9 @@ class MockRedis
177
191
 
178
192
  def with_sets_at(*keys, &blk)
179
193
  keys = keys.flatten
194
+
195
+ assert_type(*keys)
196
+
180
197
  if keys.length == 1
181
198
  with_set_at(keys.first, &blk)
182
199
  else
@@ -195,8 +212,7 @@ class MockRedis
195
212
  def assert_sety(key)
196
213
  unless sety?(key)
197
214
  # Not the most helpful error, but it's what redis-rb barfs up
198
- raise Redis::CommandError,
199
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
215
+ raise Error.wrong_type_error(self)
200
216
  end
201
217
  end
202
218
  end
@@ -5,7 +5,7 @@ class MockRedis
5
5
  include Assertions
6
6
 
7
7
  def sort(key, options = {})
8
- return [] if key.nil?
8
+ assert_type(key)
9
9
 
10
10
  enumerable = data[key]
11
11
 
@@ -35,6 +35,8 @@ class MockRedis
35
35
  stream.add id, entry
36
36
  stream.trim opts[:maxlen] if opts[:maxlen]
37
37
  return stream.last_id
38
+ rescue Redis::CommandError => e
39
+ raise Error.command_error(e.message, self)
38
40
  end
39
41
  end
40
42
 
@@ -55,6 +57,8 @@ class MockRedis
55
57
  args += ['COUNT', count] if count
56
58
  with_stream_at(key) do |stream|
57
59
  return stream.range(*args)
60
+ rescue Redis::CommandError => e
61
+ raise Error.command_error(e.message, self)
58
62
  end
59
63
  end
60
64
 
@@ -63,6 +67,8 @@ class MockRedis
63
67
  args += ['COUNT', count] if count
64
68
  with_stream_at(key) do |stream|
65
69
  return stream.range(*args)
70
+ rescue Redis::CommandError => e
71
+ raise Error.command_error(e.message, self)
66
72
  end
67
73
  end
68
74
 
@@ -94,8 +100,7 @@ class MockRedis
94
100
 
95
101
  def assert_streamy(key)
96
102
  unless streamy?(key)
97
- raise Redis::CommandError,
98
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
103
+ raise Error.wrong_type_error(self)
99
104
  end
100
105
  end
101
106
  end
@@ -14,7 +14,7 @@ class MockRedis
14
14
 
15
15
  def bitfield(*args)
16
16
  if args.length < 4
17
- raise Redis::CommandError, 'ERR wrong number of arguments for BITFIELD'
17
+ raise Error.command_error('ERR wrong number of arguments for BITFIELD', self)
18
18
  end
19
19
 
20
20
  key = args.shift
@@ -28,7 +28,7 @@ class MockRedis
28
28
  new_overflow_method = args.shift.to_s.downcase
29
29
 
30
30
  unless %w[wrap sat fail].include? new_overflow_method
31
- raise Redis::CommandError, 'ERR Invalid OVERFLOW type specified'
31
+ raise Error.command_error('ERR Invalid OVERFLOW type specified', self)
32
32
  end
33
33
 
34
34
  overflow_method = new_overflow_method
@@ -41,9 +41,11 @@ class MockRedis
41
41
  type_size = type[1..].to_i
42
42
 
43
43
  if (type_size > 64 && is_signed) || (type_size >= 64 && !is_signed)
44
- raise Redis::CommandError,
44
+ raise Error.command_error(
45
45
  'ERR Invalid bitfield type. Use something like i16 u8. ' \
46
- 'Note that u64 is not supported but i64 is.'
46
+ 'Note that u64 is not supported but i64 is.',
47
+ self
48
+ )
47
49
  end
48
50
 
49
51
  if offset.to_s[0] == '#'
@@ -130,12 +132,10 @@ class MockRedis
130
132
 
131
133
  def incrby(key, n)
132
134
  assert_stringy(key)
133
- unless can_incr?(data[key])
134
- raise Redis::CommandError, 'ERR value is not an integer or out of range'
135
- end
135
+ n = Integer(n)
136
136
 
137
- unless looks_like_integer?(n.to_s)
138
- raise Redis::CommandError, 'ERR value is not an integer or out of range'
137
+ unless can_incr?(data[key])
138
+ raise Error.command_error('ERR value is not an integer or out of range', self)
139
139
  end
140
140
 
141
141
  new_value = data[key].to_i + n.to_i
@@ -146,12 +146,9 @@ class MockRedis
146
146
 
147
147
  def incrbyfloat(key, n)
148
148
  assert_stringy(key)
149
+ n = Float(n)
149
150
  unless can_incr_float?(data[key])
150
- raise Redis::CommandError, 'ERR value is not a valid float'
151
- end
152
-
153
- unless looks_like_float?(n.to_s)
154
- raise Redis::CommandError, 'ERR value is not a valid float'
151
+ raise Error.command_error('ERR value is not a valid float', self)
155
152
  end
156
153
 
157
154
  new_value = data[key].to_f + n.to_f
@@ -181,7 +178,7 @@ class MockRedis
181
178
  kvpairs = kvpairs.first if kvpairs.size == 1 && kvpairs.first.is_a?(Enumerable)
182
179
 
183
180
  if kvpairs.length.odd?
184
- raise Redis::CommandError, 'ERR wrong number of arguments for MSET'
181
+ raise Error.command_error('ERR wrong number of arguments for MSET', self)
185
182
  end
186
183
 
187
184
  kvpairs.each_slice(2) do |(k, v)|
@@ -237,28 +234,28 @@ class MockRedis
237
234
  remove_expiration(key) unless keepttl
238
235
  if ex
239
236
  if ex == 0
240
- raise Redis::CommandError, 'ERR invalid expire time in set'
237
+ raise Error.command_error('ERR invalid expire time in set', self)
241
238
  end
242
239
  expire(key, ex)
243
240
  end
244
241
 
245
242
  if px
246
243
  if px == 0
247
- raise Redis::CommandError, 'ERR invalid expire time in set'
244
+ raise Error.command_error('ERR invalid expire time in set', self)
248
245
  end
249
246
  pexpire(key, px)
250
247
  end
251
248
 
252
249
  if exat
253
250
  if exat == 0
254
- raise Redis::CommandError, 'ERR invalid expire time in set'
251
+ raise Error.command_error('ERR invalid expire time in set', self)
255
252
  end
256
253
  expireat(key, exat)
257
254
  end
258
255
 
259
256
  if pxat
260
257
  if pxat == 0
261
- raise Redis::CommandError, 'ERR invalid expire time in set'
258
+ raise Error.command_error('ERR invalid expire time in set', self)
262
259
  end
263
260
  pexpireat(key, pxat)
264
261
  end
@@ -347,7 +344,7 @@ class MockRedis
347
344
 
348
345
  def setex(key, seconds, value)
349
346
  if seconds <= 0
350
- raise Redis::CommandError, 'ERR invalid expire time in setex'
347
+ raise Error.command_error('ERR invalid expire time in setex', self)
351
348
  else
352
349
  set(key, value)
353
350
  expire(key, seconds)
@@ -357,7 +354,7 @@ class MockRedis
357
354
 
358
355
  def psetex(key, milliseconds, value)
359
356
  if milliseconds <= 0
360
- raise Redis::CommandError, 'ERR invalid expire time in psetex'
357
+ raise Error.command_error('ERR invalid expire time in psetex', self)
361
358
  else
362
359
  set(key, value)
363
360
  pexpire(key, milliseconds)
@@ -377,7 +374,7 @@ class MockRedis
377
374
  def setrange(key, offset, value)
378
375
  assert_stringy(key)
379
376
  value = value.to_s
380
- old_value = (data[key] || '')
377
+ old_value = data[key] || ''
381
378
 
382
379
  prefix = zero_pad(old_value[0...offset], offset)
383
380
  data[key] = prefix + value + (old_value[(offset + value.length)..] || '')
@@ -395,10 +392,13 @@ class MockRedis
395
392
  data[key].nil? || data[key].is_a?(String)
396
393
  end
397
394
 
398
- def assert_stringy(key,
399
- message = 'WRONGTYPE Operation against a key holding the wrong kind of value')
395
+ def assert_stringy(key, message = nil)
400
396
  unless stringy?(key)
401
- raise Redis::CommandError, message
397
+ if message
398
+ raise Error.command_error(message, self)
399
+ else
400
+ raise Error.wrong_type_error(self)
401
+ end
402
402
  end
403
403
  end
404
404
 
@@ -1,4 +1,5 @@
1
1
  require 'mock_redis/undef_redis_methods'
2
+ require 'mock_redis/error'
2
3
 
3
4
  class MockRedis
4
5
  class TransactionWrapper
@@ -12,18 +13,12 @@ class MockRedis
12
13
  @db = db
13
14
  @transaction_futures = []
14
15
  @multi_stack = []
15
- @multi_block_given = false
16
16
  end
17
17
 
18
18
  ruby2_keywords def method_missing(method, *args, &block)
19
19
  if in_multi?
20
- future = MockRedis::Future.new([method, *args], block)
21
- @transaction_futures << future
22
-
23
- if @multi_block_given
24
- future
25
- else
26
- 'QUEUED'
20
+ MockRedis::Future.new([method, *args], block).tap do |future|
21
+ @transaction_futures << future
27
22
  end
28
23
  else
29
24
  @db.expire_keys
@@ -40,7 +35,7 @@ class MockRedis
40
35
 
41
36
  def discard
42
37
  unless in_multi?
43
- raise Redis::CommandError, 'ERR DISCARD without MULTI'
38
+ raise Error.command_error('ERR DISCARD without MULTI', self)
44
39
  end
45
40
  pop_multi
46
41
 
@@ -50,22 +45,23 @@ class MockRedis
50
45
 
51
46
  def exec
52
47
  unless in_multi?
53
- raise Redis::CommandError, 'ERR EXEC without MULTI'
48
+ raise Error.command_error('ERR EXEC without MULTI', self)
54
49
  end
50
+
55
51
  pop_multi
56
52
  return if in_multi?
57
- @multi_block_given = false
58
53
 
59
54
  responses = @transaction_futures.map do |future|
60
55
  result = send(*future.command)
61
56
  future.store_result(result)
62
57
  future.value
63
- rescue StandardError => e
64
- e
65
58
  end
66
59
 
67
- @transaction_futures = []
68
60
  responses
61
+ ensure
62
+ # At this point, multi is done, so we can't call discard anymore.
63
+ # Therefore, we need to clear the transaction futures manually.
64
+ @transaction_futures = []
69
65
  end
70
66
 
71
67
  def in_multi?
@@ -81,20 +77,16 @@ class MockRedis
81
77
  end
82
78
 
83
79
  def multi
84
- if block_given?
85
- push_multi
86
- @multi_block_given = true
87
- begin
88
- yield(self)
89
- exec
90
- rescue StandardError => e
91
- discard
92
- raise e
93
- end
94
- else
95
- raise Redis::CommandError, 'ERR MULTI calls can not be nested' if in_multi?
96
- push_multi
97
- 'OK'
80
+ raise Redis::BaseError, "Can't nest multi transaction" if in_multi?
81
+
82
+ push_multi
83
+
84
+ begin
85
+ yield(self)
86
+ exec
87
+ rescue StandardError => e
88
+ discard if in_multi?
89
+ raise e
98
90
  end
99
91
  end
100
92
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  class MockRedis
5
- VERSION = '0.45.0'
5
+ VERSION = '0.47.0'
6
6
  end
@@ -12,7 +12,10 @@ class MockRedis
12
12
  zadd_options = args.pop if args.last.is_a?(Hash)
13
13
 
14
14
  if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
15
- raise Redis::CommandError, 'ERR XX and NX options at the same time are not compatible'
15
+ raise Error.command_error(
16
+ 'ERR XX and NX options at the same time are not compatible',
17
+ self
18
+ )
16
19
  end
17
20
 
18
21
  if args.size == 1 && args[0].is_a?(Array)
@@ -21,7 +24,7 @@ class MockRedis
21
24
  score, member = args
22
25
  zadd_one_member(key, score, member, zadd_options)
23
26
  else
24
- raise Redis::CommandError, 'ERR wrong number of arguments'
27
+ raise ArgumentError, 'wrong number of arguments'
25
28
  end
26
29
  end
27
30
 
@@ -57,12 +60,13 @@ class MockRedis
57
60
  private :zadd_one_member
58
61
 
59
62
  def zadd_multiple_members(key, args, zadd_options = {})
60
- assert_has_args(args, 'zadd')
61
-
62
63
  args = args.each_slice(2).to_a unless args.first.is_a?(Array)
63
64
  with_zset_at(key) do |zset|
64
65
  if zadd_options[:incr]
65
- raise Redis::CommandError, 'ERR INCR option supports a single increment-element pair'
66
+ raise Error.command_error(
67
+ 'ERR INCR option supports a single increment-element pair',
68
+ self
69
+ )
66
70
  elsif zadd_options[:xx]
67
71
  args.each { |score, member| zset.include?(member) && zset.add(score, member.to_s) }
68
72
  0
@@ -143,7 +147,7 @@ class MockRedis
143
147
  else
144
148
  args = args.first
145
149
  if args.empty?
146
- raise Redis::CommandError, "ERR wrong number of arguments for 'zrem' command"
150
+ retval = 0
147
151
  else
148
152
  retval = args.map { |member| !!zscore(key, member.to_s) }.count(true)
149
153
  with_zset_at(key) do |z|
@@ -257,7 +261,7 @@ class MockRedis
257
261
  offset, count = limit
258
262
  collection.drop(offset).take(count)
259
263
  else
260
- raise Redis::CommandError, 'ERR syntax error'
264
+ raise Error.syntax_error(self)
261
265
  end
262
266
  else
263
267
  collection
@@ -277,7 +281,7 @@ class MockRedis
277
281
  def combine_weighted_zsets(keys, options, how)
278
282
  weights = options.fetch(:weights, keys.map { 1 })
279
283
  if weights.length != keys.length
280
- raise Redis::CommandError, 'ERR syntax error'
284
+ raise Error.syntax_error(self)
281
285
  end
282
286
 
283
287
  aggregator = case options.fetch(:aggregate, :sum).to_s.downcase.to_sym
@@ -288,7 +292,7 @@ class MockRedis
288
292
  when :max
289
293
  proc { |a, b| [a, b].compact.max }
290
294
  else
291
- raise Redis::CommandError, 'ERR syntax error'
295
+ raise Error.syntax_error(self)
292
296
  end
293
297
 
294
298
  with_zsets_at(*keys, coercible: true) do |*zsets|
@@ -342,15 +346,13 @@ class MockRedis
342
346
 
343
347
  def assert_zsety(key)
344
348
  unless zsety?(key)
345
- raise Redis::CommandError,
346
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
349
+ raise Error.wrong_type_error(self)
347
350
  end
348
351
  end
349
352
 
350
353
  def assert_coercible_zsety(key)
351
354
  unless coercible_zsety?(key)
352
- raise Redis::CommandError,
353
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
355
+ raise Error.wrong_type_error(self)
354
356
  end
355
357
  end
356
358
 
@@ -363,9 +365,10 @@ class MockRedis
363
365
  return if value.to_s =~ /\(?(-|\+)inf/
364
366
 
365
367
  value = $1 if value.to_s =~ /\((.*)/
366
- unless looks_like_float?(value)
367
- raise Redis::CommandError, message
368
- end
368
+
369
+ assert_type(value)
370
+
371
+ raise Error.command_error(message, self) unless looks_like_float?(value)
369
372
  end
370
373
 
371
374
  def assert_range_args(min, max)
data/lib/mock_redis.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'set'
2
2
 
3
3
  require 'mock_redis/assertions'
4
+ require 'mock_redis/error'
4
5
  require 'mock_redis/database'
5
6
  require 'mock_redis/expire_wrapper'
6
7
  require 'mock_redis/future'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mock_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.45.0
4
+ version: 0.47.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-09-13 00:00:00.000000000 Z
12
+ date: 2024-11-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: 4.8.0
34
+ version: '5'
35
35
  type: :development
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 4.8.0
41
+ version: '5'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rspec
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +96,7 @@ files:
96
96
  - lib/mock_redis/assertions.rb
97
97
  - lib/mock_redis/connection_method.rb
98
98
  - lib/mock_redis/database.rb
99
+ - lib/mock_redis/error.rb
99
100
  - lib/mock_redis/exceptions.rb
100
101
  - lib/mock_redis/expire_wrapper.rb
101
102
  - lib/mock_redis/future.rb
@@ -124,10 +125,10 @@ licenses:
124
125
  - MIT
125
126
  metadata:
126
127
  bug_tracker_uri: https://github.com/sds/mock_redis/issues
127
- changelog_uri: https://github.com/sds/mock_redis/blob/v0.45.0/CHANGELOG.md
128
- documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.45.0
128
+ changelog_uri: https://github.com/sds/mock_redis/blob/v0.47.0/CHANGELOG.md
129
+ documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.47.0
129
130
  homepage_uri: https://github.com/sds/mock_redis
130
- source_code_uri: https://github.com/sds/mock_redis/tree/v0.45.0
131
+ source_code_uri: https://github.com/sds/mock_redis/tree/v0.47.0
131
132
  post_install_message:
132
133
  rdoc_options: []
133
134
  require_paths: