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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +3 -2
- data/lib/mock_redis/assertions.rb +19 -3
- data/lib/mock_redis/database.rb +17 -22
- data/lib/mock_redis/error.rb +27 -0
- data/lib/mock_redis/geospatial_methods.rb +9 -7
- data/lib/mock_redis/hash_methods.rb +50 -15
- data/lib/mock_redis/list_methods.rb +16 -17
- data/lib/mock_redis/set_methods.rb +23 -7
- data/lib/mock_redis/sort_method.rb +1 -1
- data/lib/mock_redis/stream_methods.rb +7 -2
- data/lib/mock_redis/string_methods.rb +25 -25
- data/lib/mock_redis/transaction_wrapper.rb +20 -28
- data/lib/mock_redis/version.rb +1 -1
- data/lib/mock_redis/zset_methods.rb +19 -16
- data/lib/mock_redis.rb +1 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bbc0edb10b18ba89981fe78e35e169dbbdc4af0886e01f07e0947f8eb7eff45
|
4
|
+
data.tar.gz: 88d094d92b9c55ebc677dca8c2a6561805b79dbf11d97b2992a9c1312cccc1b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
7
|
-
raise
|
8
|
-
|
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
|
data/lib/mock_redis/database.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
118
|
+
timestamp_ms = Integer(timestamp_ms)
|
119
119
|
|
120
120
|
if nx && gt || gt && lt || lt && nx || nx && xx
|
121
|
-
raise
|
122
|
-
ERR NX and XX, GT or LT options at the same time are not compatible
|
123
|
-
|
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
|
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
|
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
|
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
|
-
|
313
|
-
|
314
|
-
|
315
|
-
raise
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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.
|
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
|
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
|
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,
|
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
|
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
|
-
|
103
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
239
|
-
|
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.
|
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
|
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
|
@@ -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
|
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
|
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
|
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
|
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
|
-
|
134
|
-
raise Redis::CommandError, 'ERR value is not an integer or out of range'
|
135
|
-
end
|
135
|
+
n = Integer(n)
|
136
136
|
|
137
|
-
unless
|
138
|
-
raise
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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 =
|
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
|
-
|
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
|
-
|
21
|
-
|
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
|
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
|
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
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
|
data/lib/mock_redis/version.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
367
|
-
|
368
|
-
|
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
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.
|
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-
|
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:
|
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:
|
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.
|
128
|
-
documentation_uri: https://www.rubydoc.info/gems/mock_redis/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.
|
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:
|