mock_redis 0.55.0 → 0.56.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f61c25ac91aa99558a90e5035269dbabc36c565d7aec85e78356a2178c5aa1a6
4
- data.tar.gz: bdb253307125c9dc1418bc11ea5884e36d4d0e425b8243c3619816b3cc91cd48
3
+ metadata.gz: 9941cda3e99b0e5f90f9d0e0d27eb242b8af20b52beb23f52075f77b0843a2c4
4
+ data.tar.gz: 293c9b589d9876fd4c907e6e76ec73bafcf6e20cbb4af4beb2d3150b956c6a15
5
5
  SHA512:
6
- metadata.gz: 40e400a1c118712a11c638e26cdc1ce0e10cb70e34c8a390c94e904a94d1141cd152167fa465ff5e78f8c4cf31e9eeb02efc568211dbd83ae4c18a87917d61ba
7
- data.tar.gz: d68b5609c754e1c1dc0a90433e25d22466cf1a88f9733a90e8aa3daa2abe2a389b80ed32ef0daa27fd8952d2ce6c0baa80e0f28c2edb887d4e749d6b6a258689
6
+ metadata.gz: 9a9f887b2dd27f3c242498a65c121d67d18cd98727c9c3b0a208aa4405903db077b824c8103cd326d5e900323fc631a9abb88553c7a73ab7321deb97be331d0a
7
+ data.tar.gz: c80f28279ed2aff3cac655ecaa3d8d7e56c08ae7f24d5a0403b8de63543e627b07b698eea6f3788d59734ac23575930e6d6c0c78f7c17c26e2bbac82c9ef35a9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.56.0
4
+
5
+ * Add support for `redis-rb` 6 and return `GEOPOS` coordinates as floats under RESP3
6
+ * Add support for `username` option to Redis connection
7
+ * Fix `send_expires` when option is `nil`
8
+ * Fix `SET` command with positional arguments via `call` method
9
+ * Add `BYSCORE`, `BYLEX`, and `REV` options to `ZRANGE`
10
+ * Add `LT` and `CH` options to `ZADD` command
11
+ * Fix handling of nonpositive SET expirations before changing key state
12
+
3
13
  ### 0.55.0
4
14
 
5
15
  * Add `GT` option support to `ZADD`
@@ -56,6 +56,8 @@ class MockRedis
56
56
 
57
57
  if cmd_name.include?('expire')
58
58
  send_expires(command)
59
+ elsif cmd_name == 'set'
60
+ send_set(command)
59
61
  elsif cmd_name == 'info'
60
62
  # call(:info) returns a string, not a parsed hash
61
63
  info_raw(*command[1..])
@@ -362,8 +364,34 @@ class MockRedis
362
364
  end
363
365
 
364
366
  def send_expires(command)
365
- command, key, ttl, option = *command
366
- public_send(command, key, ttl, option.downcase.to_sym => option)
367
+ cmd, key, ttl, option = *command
368
+ if option
369
+ public_send(cmd, key, ttl, option.downcase.to_sym => true)
370
+ else
371
+ public_send(cmd, key, ttl)
372
+ end
373
+ end
374
+
375
+ def send_set(command)
376
+ _cmd, key, value, *options = *command
377
+ kwargs = {}
378
+
379
+ i = 0
380
+ while i < options.length
381
+ opt = options[i].to_s.downcase
382
+ case opt
383
+ when 'ex', 'px', 'exat', 'pxat'
384
+ kwargs[opt.to_sym] = options[i + 1].to_i
385
+ i += 2
386
+ when 'nx', 'xx', 'keepttl', 'get'
387
+ kwargs[opt.to_sym] = true
388
+ i += 1
389
+ else
390
+ i += 1
391
+ end
392
+ end
393
+
394
+ set(key, value, **kwargs)
367
395
  end
368
396
 
369
397
  def should_update_expiration?(expiry, new_expiry, nx:, xx:, lt:, gt:) # rubocop:disable Metrics/ParameterLists
@@ -71,9 +71,7 @@ class MockRedis
71
71
  next nil unless score
72
72
  hash = { bits: score.to_i, step: STEP }
73
73
  lng, lat = geohash_decode(hash)
74
- lng = format_decoded_coord(lng)
75
- lat = format_decoded_coord(lat)
76
- [lng, lat]
74
+ [format_decoded_coord(lng), format_decoded_coord(lat)].map { |c| resp3? ? c.to_f : c }
77
75
  end
78
76
  end
79
77
 
@@ -203,6 +201,11 @@ class MockRedis
203
201
  x | (y << 32)
204
202
  end
205
203
 
204
+ # redis-rb 6 defaults to RESP3, which returns GEO coordinates as Floats
205
+ def resp3?
206
+ @base.options.fetch(:protocol) { Redis::VERSION.to_i >= 6 ? 3 : 2 }.to_i == 3
207
+ end
208
+
206
209
  def format_decoded_coord(coord)
207
210
  coord = format('%<coord>.17f', coord: coord)
208
211
  l = 1
@@ -212,6 +212,9 @@ class MockRedis
212
212
  def set(key, value, _hash = nil, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil,
213
213
  keepttl: nil, get: nil)
214
214
  key = key.to_s
215
+ if [ex, px, exat, pxat].any? { |expiration| expiration && Integer(expiration) <= 0 }
216
+ raise Error.command_error('ERR invalid expire time in set', self)
217
+ end
215
218
  retval = self.get(key) if get
216
219
 
217
220
  return_true = false
@@ -233,30 +236,18 @@ class MockRedis
233
236
 
234
237
  remove_expiration(key) unless keepttl
235
238
  if ex
236
- if ex == 0
237
- raise Error.command_error('ERR invalid expire time in set', self)
238
- end
239
239
  expire(key, ex)
240
240
  end
241
241
 
242
242
  if px
243
- if px == 0
244
- raise Error.command_error('ERR invalid expire time in set', self)
245
- end
246
243
  pexpire(key, px)
247
244
  end
248
245
 
249
246
  if exat
250
- if exat == 0
251
- raise Error.command_error('ERR invalid expire time in set', self)
252
- end
253
247
  expireat(key, exat)
254
248
  end
255
249
 
256
250
  if pxat
257
- if pxat == 0
258
- raise Error.command_error('ERR invalid expire time in set', self)
259
- end
260
251
  pexpireat(key, pxat)
261
252
  end
262
253
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  class MockRedis
5
- VERSION = '0.55.0'
5
+ VERSION = '0.56.0'
6
6
  end
@@ -11,14 +11,28 @@ class MockRedis
11
11
  zadd_options = {}
12
12
  zadd_options = args.pop if args.last.is_a?(Hash)
13
13
 
14
- if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
14
+ if zadd_options.include?(:nx) && zadd_options.include?(:xx)
15
15
  raise Error.command_error(
16
16
  'ERR XX and NX options at the same time are not compatible',
17
17
  self
18
18
  )
19
19
  end
20
20
 
21
- if zadd_options&.include?(:gt) && zadd_options&.include?(:nx)
21
+ if zadd_options.include?(:gt) && zadd_options.include?(:nx)
22
+ raise Error.command_error(
23
+ 'ERR GT, LT, and/or NX options at the same time are not compatible',
24
+ self
25
+ )
26
+ end
27
+
28
+ if zadd_options.include?(:lt) && zadd_options.include?(:nx)
29
+ raise Error.command_error(
30
+ 'ERR GT, LT, and/or NX options at the same time are not compatible',
31
+ self
32
+ )
33
+ end
34
+
35
+ if zadd_options.include?(:gt) && zadd_options.include?(:lt)
22
36
  raise Error.command_error(
23
37
  'ERR GT, LT, and/or NX options at the same time are not compatible',
24
38
  self
@@ -58,6 +72,24 @@ class MockRedis
58
72
  return zincrby(key, score, member)
59
73
  end
60
74
 
75
+ if zadd_options[:lt]
76
+ current_score = zset.score(member.to_s)
77
+
78
+ # NOTE: does nothing if the member doesn't exist and XX option is set.
79
+ return nil if current_score.nil? && zadd_options[:xx]
80
+
81
+ # NOTE: zincrby add the increment if the member doesn't exist and XX option is not set.
82
+ return zincrby(key, score, member) if current_score.nil? && !zadd_options[:xx]
83
+
84
+ new_score = current_score + score.to_f
85
+
86
+ # NOTE: does nothing if the new score is not less than the current score.
87
+ return nil if current_score && new_score >= current_score
88
+
89
+ # NOTE: zincrby update the score if the new score is less than the current score.
90
+ return zincrby(key, score, member)
91
+ end
92
+
61
93
  if zadd_options[:xx]
62
94
  member_present = zset.include?(member)
63
95
  return member_present ? zincrby(key, score, member) : nil
@@ -79,8 +111,23 @@ class MockRedis
79
111
  zset.add(new_score, member.to_s)
80
112
  true
81
113
  else
82
- zset.add(new_score, member.to_s) if new_score > current_score
83
- false
114
+ changed = new_score > current_score
115
+ zset.add(new_score, member.to_s) if changed
116
+ zadd_options[:ch] ? changed : false
117
+ end
118
+ elsif zadd_options[:lt]
119
+ current_score = zset.score(member.to_s)
120
+ new_score = score.to_f
121
+
122
+ if current_score.nil?
123
+ return false if zadd_options[:xx]
124
+
125
+ zset.add(new_score, member.to_s)
126
+ true
127
+ else
128
+ changed = new_score < current_score
129
+ zset.add(new_score, member.to_s) if changed
130
+ zadd_options[:ch] ? changed : false
84
131
  end
85
132
  elsif zadd_options[:xx]
86
133
  # NOTE: gt and xx options are handled at the gt branch above, so its not handled here.
@@ -89,9 +136,14 @@ class MockRedis
89
136
  elsif zadd_options[:nx]
90
137
  !zset.include?(member) && !!zset.add(score, member.to_s)
91
138
  else
92
- retval = !zscore(key, member)
139
+ is_new = !zscore(key, member)
140
+ old_score = zset.score(member.to_s)
93
141
  zset.add(score, member.to_s)
94
- retval
142
+ if zadd_options[:ch]
143
+ is_new || (old_score && old_score != score.to_f) # rubocop:disable Lint/FloatComparison
144
+ else
145
+ is_new
146
+ end
95
147
  end
96
148
  end
97
149
  end
@@ -116,13 +168,44 @@ class MockRedis
116
168
  end
117
169
  else
118
170
  score_f = score.to_f
119
- zset.add(score_f, member.to_s) if score_f > current_score
171
+ if score_f > current_score
172
+ zset.add(score_f, member.to_s)
173
+ retval += 1 if zadd_options[:ch]
174
+ end
175
+ end
176
+ retval
177
+ end
178
+ elsif zadd_options[:lt]
179
+ args.reduce(0) do |retval, (score, member)|
180
+ current_score = zset.score(member.to_s)
181
+ if current_score.nil?
182
+ unless zadd_options[:xx]
183
+ zset.add(score.to_f, member.to_s)
184
+ retval += 1
185
+ end
186
+ else
187
+ score_f = score.to_f
188
+ if score_f < current_score
189
+ zset.add(score_f, member.to_s)
190
+ retval += 1 if zadd_options[:ch]
191
+ end
120
192
  end
121
193
  retval
122
194
  end
123
195
  elsif zadd_options[:xx]
124
- args.each { |score, member| zset.include?(member) && zset.add(score, member.to_s) }
125
- 0
196
+ if zadd_options[:ch]
197
+ args.reduce(0) do |retval, (score, member)|
198
+ if zset.include?(member)
199
+ old_score = zset.score(member.to_s)
200
+ zset.add(score, member.to_s)
201
+ retval += 1 if old_score != score.to_f # rubocop:disable Lint/FloatComparison
202
+ end
203
+ retval
204
+ end
205
+ else
206
+ args.each { |score, member| zset.include?(member) && zset.add(score, member.to_s) }
207
+ 0
208
+ end
126
209
  elsif zadd_options[:nx]
127
210
  args.reduce(0) do |retval, (score, member)|
128
211
  unless zset.include?(member)
@@ -133,8 +216,13 @@ class MockRedis
133
216
  end
134
217
  else
135
218
  args.reduce(0) do |retval, (score, member)|
136
- retval += 1 unless zset.include?(member)
219
+ is_new = !zset.include?(member)
220
+ old_score = zset.score(member.to_s)
137
221
  zset.add(score, member.to_s)
222
+ # rubocop:disable Lint/FloatComparison
223
+ changed = is_new || (zadd_options[:ch] && old_score && old_score != score.to_f)
224
+ # rubocop:enable Lint/FloatComparison
225
+ retval += 1 if changed
138
226
  retval
139
227
  end
140
228
  end
@@ -175,9 +263,50 @@ class MockRedis
175
263
 
176
264
  def zrange(key, start, stop, options = {})
177
265
  with_zset_at(key) do |z|
178
- start = [start.to_i, -z.sorted.size].max
179
- stop = stop.to_i
180
- to_response(z.sorted[start..stop] || [], options)
266
+ if options[:byscore]
267
+ # With REV, arguments are (max, min) not (min, max)
268
+ min = options[:rev] ? stop : start
269
+ max = options[:rev] ? start : stop
270
+ assert_range_args(min, max)
271
+ results = z.in_range(min, max)
272
+ results = results.reverse if options[:rev]
273
+ results = apply_limit(results, options[:limit])
274
+ to_response(results, options)
275
+ elsif options[:bylex]
276
+ # With REV, arguments are (max, min) not (min, max)
277
+ lex_min = options[:rev] ? stop : start
278
+ lex_max = options[:rev] ? start : stop
279
+ results = z.sorted.select do |_score, member|
280
+ in_lex_range?(member, lex_min, lex_max)
281
+ end
282
+ results = results.reverse if options[:rev]
283
+ results = apply_limit(results, options[:limit])
284
+ to_response(results, options)
285
+ else
286
+ sorted = options[:rev] ? z.sorted.reverse : z.sorted
287
+ start_idx = [start.to_i, -sorted.size].max
288
+ stop_idx = stop.to_i
289
+ to_response(sorted[start_idx..stop_idx] || [], options)
290
+ end
291
+ end
292
+ end
293
+
294
+ def in_lex_range?(member, min, max)
295
+ return false if min != '-' && !lex_compare(member, min, :min)
296
+ return false if max != '+' && !lex_compare(member, max, :max)
297
+
298
+ true
299
+ end
300
+
301
+ def lex_compare(member, bound, direction)
302
+ if bound.start_with?('[')
303
+ value = bound[1..]
304
+ direction == :min ? member >= value : member <= value
305
+ elsif bound.start_with?('(')
306
+ value = bound[1..]
307
+ direction == :min ? member > value : member < value
308
+ else
309
+ false
181
310
  end
182
311
  end
183
312
 
data/lib/mock_redis.rb CHANGED
@@ -21,6 +21,7 @@ class MockRedis
21
21
  :port => 6379,
22
22
  :path => nil,
23
23
  :timeout => 5.0,
24
+ :username => nil,
24
25
  :password => nil,
25
26
  :logger => nil,
26
27
  :db => 0,
@@ -62,6 +63,14 @@ class MockRedis
62
63
  options[:db]
63
64
  end
64
65
 
66
+ def username
67
+ options[:username]
68
+ end
69
+
70
+ def password
71
+ options[:password]
72
+ end
73
+
65
74
  def logger
66
75
  options[:logger]
67
76
  end
@@ -125,6 +134,7 @@ class MockRedis
125
134
  defaults[:scheme] = uri.scheme
126
135
  defaults[:host] = uri.host
127
136
  defaults[:port] = uri.port if uri.port
137
+ defaults[:username] = uri.user if uri.user
128
138
  defaults[:password] = uri.password if uri.password
129
139
  defaults[:db] = uri.path[1..].to_i if uri.path
130
140
  end
metadata CHANGED
@@ -1,30 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mock_redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.55.0
4
+ version: 0.56.0
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: 2026-06-04 00:00:00.000000000 Z
12
+ date: 2026-11-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '5'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '7'
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - "~>"
28
+ - - ">="
26
29
  - !ruby/object:Gem::Version
27
30
  version: '5'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '7'
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: rake
30
36
  requirement: !ruby/object:Gem::Requirement
@@ -125,11 +131,11 @@ licenses:
125
131
  - MIT
126
132
  metadata:
127
133
  bug_tracker_uri: https://github.com/sds/mock_redis/issues
128
- changelog_uri: https://github.com/sds/mock_redis/blob/v0.55.0/CHANGELOG.md
129
- documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.55.0
134
+ changelog_uri: https://github.com/sds/mock_redis/blob/v0.56.0/CHANGELOG.md
135
+ documentation_uri: https://www.rubydoc.info/gems/mock_redis/0.56.0
130
136
  homepage_uri: https://github.com/sds/mock_redis
131
- source_code_uri: https://github.com/sds/mock_redis/tree/v0.55.0
132
- post_install_message:
137
+ source_code_uri: https://github.com/sds/mock_redis/tree/v0.56.0
138
+ post_install_message:
133
139
  rdoc_options: []
134
140
  require_paths:
135
141
  - lib
@@ -144,8 +150,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
150
  - !ruby/object:Gem::Version
145
151
  version: '0'
146
152
  requirements: []
147
- rubygems_version: 3.0.3.1
148
- signing_key:
153
+ rubygems_version: 3.5.22
154
+ signing_key:
149
155
  specification_version: 4
150
156
  summary: Redis mock that just lives in memory; useful for testing.
151
157
  test_files: []