mock_redis 0.46.0 → 0.48.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ced935cd314aee4b33828b9e04c890b4bf4cf36b6e09b93cf6b3b3a3a0bd619a
4
- data.tar.gz: def8f4648486a13f063a906df2fade9e33f703e9e5c019516f227049217c64b2
3
+ metadata.gz: 9b443f014758e0bcf36ab97b1b52c8835e445b6d08c3934f00171a9d0c2ab8b8
4
+ data.tar.gz: 401f2c621a340ae75c59409943718a6fdcfbb76c816d38651f93d9548e086d71
5
5
  SHA512:
6
- metadata.gz: c73c851717ac57510e528adad4f5a6f4277abef68338d49be40871bc366937270f90d977c777e18eb0b2d0d0b1e787f3a8e3cd59a51886a57c045f655211cebe
7
- data.tar.gz: b47fbb5cffd0d47270915cb786ac50fa30dd00fc1b064d82b1e3d754ad746b2da2a1cbdbc53e5210cd098174b7e1bac2ecbc880ee32576fe24eeea4b7e0376ed
6
+ metadata.gz: 52ea483bccaab28544bfa90773463085afea971730b8ccb715127cb1815d6de4e007f6d4fac9b055aa2e659720a24618e392a96992875cc77d36d151e9389c32
7
+ data.tar.gz: 141763ba4434ed736d5f28f50e9ec5b012d040fdf0aaa00c8a11a736165a02db7912624841fb319109bec1465aaeb05a49b48adb6c74b08dbfb1976d715bb031
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.48.1
4
+
5
+ * Fix load issue with `redis-client` gem
6
+
7
+ ### 0.48.0
8
+
9
+ * Add support for `lmpop` on Redis 7+
10
+
11
+ ### 0.47.0
12
+
13
+ * Add support for `redis-rb` gem versions 5.x
14
+ * Drop support for `redis-rb` gem versions < 5.x
15
+
3
16
  ### 0.46.0
4
17
 
5
18
  * Fix `hset` for array of key-value pairs
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,28 @@
1
+ require 'redis-client'
2
+ require 'mock_redis/error'
3
+
1
4
  class MockRedis
5
+ DUMP_TYPES = RedisClient::RESP3::DUMP_TYPES
6
+
2
7
  module Assertions
3
8
  private
4
9
 
5
10
  def assert_has_args(args, command)
6
- unless args.any?
7
- raise Redis::CommandError,
8
- "ERR wrong number of arguments for '#{command}' command"
11
+ if args.empty?
12
+ raise Error.command_error(
13
+ "ERR wrong number of arguments for '#{command}' command",
14
+ self
15
+ )
16
+ end
17
+ end
18
+
19
+ def assert_type(*args)
20
+ args.each do |arg|
21
+ DUMP_TYPES.fetch(arg.class) do |unexpected_class|
22
+ unless DUMP_TYPES.keys.find { |t| t > unexpected_class }
23
+ raise TypeError, "Unsupported command argument type: #{unexpected_class}"
24
+ end
25
+ end
9
26
  end
10
27
  end
11
28
  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
@@ -138,11 +166,15 @@ class MockRedis
138
166
  def hset(key, *args)
139
167
  added = 0
140
168
  args.flatten!(1)
169
+ assert_type(key)
170
+
141
171
  with_hash_at(key) do |hash|
142
172
  if args.length == 1 && args[0].is_a?(Hash)
143
173
  args = args[0].to_a.flatten
144
174
  end
145
175
 
176
+ assert_type(*args)
177
+
146
178
  args.each_slice(2) do |field, value|
147
179
  added += 1 unless hash.key?(field.to_s)
148
180
  hash[field.to_s] = value.to_s
@@ -152,6 +184,7 @@ class MockRedis
152
184
  end
153
185
 
154
186
  def hsetnx(key, field, value)
187
+ assert_type(key, field, value)
155
188
  if hget(key, field)
156
189
  false
157
190
  else
@@ -161,6 +194,8 @@ class MockRedis
161
194
  end
162
195
 
163
196
  def hvals(key)
197
+ assert_type(key)
198
+
164
199
  with_hash_at(key, &:values)
165
200
  end
166
201
 
@@ -176,8 +211,7 @@ class MockRedis
176
211
 
177
212
  def assert_hashy(key)
178
213
  unless hashy?(key)
179
- raise Redis::CommandError,
180
- 'WRONGTYPE Operation against a key holding the wrong kind of value'
214
+ raise Error.wrong_type_error(self)
181
215
  end
182
216
  end
183
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)
@@ -92,6 +90,32 @@ class MockRedis
92
90
  with_list_at(key, &:length)
93
91
  end
94
92
 
93
+ def lmpop(*keys, **options)
94
+ keys.each do |key|
95
+ assert_listy(key)
96
+ end
97
+
98
+ modifier = options.is_a?(Hash) && options[:modifier]&.to_s&.downcase || 'left'
99
+ count = (options.is_a?(Hash) && options[:count]) || 1
100
+
101
+ unless %w[left right].include?(modifier)
102
+ raise Redis::CommandError, 'ERR syntax error'
103
+ end
104
+
105
+ keys.each do |key|
106
+ record_count = llen(key)
107
+ next if record_count.zero?
108
+
109
+ values = [count, record_count].min.times.map do
110
+ modifier == 'left' ? with_list_at(key, &:shift) : with_list_at(key, &:pop)
111
+ end
112
+
113
+ return [key, values]
114
+ end
115
+
116
+ nil
117
+ end
118
+
95
119
  def lmove(source, destination, wherefrom, whereto)
96
120
  assert_listy(source)
97
121
  assert_listy(destination)
@@ -99,9 +123,8 @@ class MockRedis
99
123
  wherefrom = wherefrom.to_s.downcase
100
124
  whereto = whereto.to_s.downcase
101
125
 
102
- unless %w[left right].include?(wherefrom) && %w[left right].include?(whereto)
103
- raise Redis::CommandError, 'ERR syntax error'
104
- end
126
+ assert_where_field(wherefrom, 'where_source')
127
+ assert_where_field(whereto, 'where_destination')
105
128
 
106
129
  value = wherefrom == 'left' ? lpop(source) : rpop(source)
107
130
  (whereto == 'left' ? lpush(destination, value) : rpush(destination, value)) unless value.nil?
@@ -127,7 +150,7 @@ class MockRedis
127
150
  def lpushx(key, value)
128
151
  value = [value] unless value.is_a?(Array)
129
152
  if value.empty?
130
- raise Redis::CommandError, "ERR wrong number of arguments for 'lpushx' command"
153
+ raise Error.command_error("ERR wrong number of arguments for 'lpushx' command", self)
131
154
  end
132
155
  assert_listy(key)
133
156
  return 0 unless list_at?(key)
@@ -140,10 +163,7 @@ class MockRedis
140
163
  end
141
164
 
142
165
  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
166
+ count = Integer(count)
147
167
  value = value.to_s
148
168
 
149
169
  with_list_at(key) do |list|
@@ -167,12 +187,12 @@ class MockRedis
167
187
  assert_listy(key)
168
188
 
169
189
  unless list_at?(key)
170
- raise Redis::CommandError, 'ERR no such key'
190
+ raise Error.command_error('ERR no such key', self)
171
191
  end
172
192
 
173
193
  index = index.to_i
174
194
  unless (0...llen(key)).cover?(index)
175
- raise Redis::CommandError, 'ERR index out of range'
195
+ raise Error.command_error('ERR index out of range', self)
176
196
  end
177
197
 
178
198
  data[key][index] = value.to_s
@@ -211,7 +231,7 @@ class MockRedis
211
231
  def rpushx(key, value)
212
232
  value = [value] unless value.is_a?(Array)
213
233
  if value.empty?
214
- raise Redis::CommandError, "ERR wrong number of arguments for 'rpushx' command"
234
+ raise Error.command_error("ERR wrong number of arguments for 'rpushx' command", self)
215
235
  end
216
236
  assert_listy(key)
217
237
  return 0 unless list_at?(key)
@@ -235,8 +255,13 @@ class MockRedis
235
255
  def assert_listy(key)
236
256
  unless listy?(key)
237
257
  # 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'
258
+ raise Error.wrong_type_error(self)
259
+ end
260
+ end
261
+
262
+ def assert_where_field(where, argument_name)
263
+ unless %w[left right].include?(where)
264
+ raise ArgumentError, "#{argument_name} must be 'LEFT' or 'RIGHT'"
240
265
  end
241
266
  end
242
267
 
@@ -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.46.0'
5
+ VERSION = '0.48.1'
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,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mock_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.46.0
4
+ version: 0.48.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
8
8
  - Samuel Merritt
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-11-14 00:00:00.000000000 Z
12
+ date: 2024-12-02 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,11 +125,11 @@ 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.46.0/CHANGELOG.md
128
- documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.46.0
128
+ changelog_uri: https://github.com/sds/mock_redis/blob/v0.48.1/CHANGELOG.md
129
+ documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.48.1
129
130
  homepage_uri: https://github.com/sds/mock_redis
130
- source_code_uri: https://github.com/sds/mock_redis/tree/v0.46.0
131
- post_install_message:
131
+ source_code_uri: https://github.com/sds/mock_redis/tree/v0.48.1
132
+ post_install_message:
132
133
  rdoc_options: []
133
134
  require_paths:
134
135
  - lib
@@ -143,8 +144,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
144
  - !ruby/object:Gem::Version
144
145
  version: '0'
145
146
  requirements: []
146
- rubygems_version: 3.5.9
147
- signing_key:
147
+ rubygems_version: 3.0.3.1
148
+ signing_key:
148
149
  specification_version: 4
149
150
  summary: Redis mock that just lives in memory; useful for testing.
150
151
  test_files: []