redis 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +17 -29
  4. data/.travis/Gemfile +5 -0
  5. data/CHANGELOG.md +29 -0
  6. data/Gemfile +5 -0
  7. data/README.md +1 -1
  8. data/bin/build +71 -0
  9. data/lib/redis.rb +198 -12
  10. data/lib/redis/client.rb +26 -12
  11. data/lib/redis/cluster.rb +285 -0
  12. data/lib/redis/cluster/command.rb +81 -0
  13. data/lib/redis/cluster/command_loader.rb +32 -0
  14. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  15. data/lib/redis/cluster/node.rb +104 -0
  16. data/lib/redis/cluster/node_key.rb +35 -0
  17. data/lib/redis/cluster/node_loader.rb +35 -0
  18. data/lib/redis/cluster/option.rb +76 -0
  19. data/lib/redis/cluster/slot.rb +69 -0
  20. data/lib/redis/cluster/slot_loader.rb +47 -0
  21. data/lib/redis/connection/ruby.rb +5 -2
  22. data/lib/redis/distributed.rb +10 -2
  23. data/lib/redis/errors.rb +46 -0
  24. data/lib/redis/pipeline.rb +9 -1
  25. data/lib/redis/version.rb +1 -1
  26. data/makefile +54 -22
  27. data/redis.gemspec +2 -1
  28. data/test/client_test.rb +17 -0
  29. data/test/cluster_abnormal_state_test.rb +38 -0
  30. data/test/cluster_blocking_commands_test.rb +15 -0
  31. data/test/cluster_client_internals_test.rb +77 -0
  32. data/test/cluster_client_key_hash_tags_test.rb +88 -0
  33. data/test/cluster_client_options_test.rb +147 -0
  34. data/test/cluster_client_pipelining_test.rb +59 -0
  35. data/test/cluster_client_replicas_test.rb +36 -0
  36. data/test/cluster_client_slots_test.rb +94 -0
  37. data/test/cluster_client_transactions_test.rb +71 -0
  38. data/test/cluster_commands_on_cluster_test.rb +165 -0
  39. data/test/cluster_commands_on_connection_test.rb +40 -0
  40. data/test/cluster_commands_on_geo_test.rb +74 -0
  41. data/test/cluster_commands_on_hashes_test.rb +11 -0
  42. data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
  43. data/test/cluster_commands_on_keys_test.rb +134 -0
  44. data/test/cluster_commands_on_lists_test.rb +15 -0
  45. data/test/cluster_commands_on_pub_sub_test.rb +101 -0
  46. data/test/cluster_commands_on_scripting_test.rb +56 -0
  47. data/test/cluster_commands_on_server_test.rb +221 -0
  48. data/test/cluster_commands_on_sets_test.rb +39 -0
  49. data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
  50. data/test/cluster_commands_on_streams_test.rb +196 -0
  51. data/test/cluster_commands_on_strings_test.rb +15 -0
  52. data/test/cluster_commands_on_transactions_test.rb +41 -0
  53. data/test/cluster_commands_on_value_types_test.rb +14 -0
  54. data/test/commands_on_geo_test.rb +116 -0
  55. data/test/commands_on_hashes_test.rb +2 -14
  56. data/test/commands_on_hyper_log_log_test.rb +2 -14
  57. data/test/commands_on_lists_test.rb +2 -13
  58. data/test/commands_on_sets_test.rb +2 -70
  59. data/test/commands_on_sorted_sets_test.rb +2 -145
  60. data/test/commands_on_strings_test.rb +2 -94
  61. data/test/commands_on_value_types_test.rb +36 -0
  62. data/test/distributed_blocking_commands_test.rb +8 -0
  63. data/test/distributed_commands_on_hashes_test.rb +16 -3
  64. data/test/distributed_commands_on_hyper_log_log_test.rb +8 -13
  65. data/test/distributed_commands_on_lists_test.rb +4 -5
  66. data/test/distributed_commands_on_sets_test.rb +45 -46
  67. data/test/distributed_commands_on_sorted_sets_test.rb +51 -8
  68. data/test/distributed_commands_on_strings_test.rb +10 -0
  69. data/test/distributed_commands_on_value_types_test.rb +36 -0
  70. data/test/helper.rb +176 -32
  71. data/test/internals_test.rb +20 -1
  72. data/test/lint/blocking_commands.rb +40 -16
  73. data/test/lint/hashes.rb +41 -0
  74. data/test/lint/hyper_log_log.rb +15 -1
  75. data/test/lint/lists.rb +16 -0
  76. data/test/lint/sets.rb +142 -0
  77. data/test/lint/sorted_sets.rb +183 -2
  78. data/test/lint/strings.rb +102 -0
  79. data/test/pipelining_commands_test.rb +8 -0
  80. data/test/support/cluster/orchestrator.rb +199 -0
  81. data/test/support/redis_mock.rb +1 -1
  82. data/test/transactions_test.rb +10 -0
  83. metadata +81 -2
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ # ruby -w -Itest test/cluster_commands_on_server_test.rb
6
+ # @see https://redis.io/commands#server
7
+ class TestClusterCommandsOnServer < Test::Unit::TestCase
8
+ include Helper::Cluster
9
+
10
+ def test_bgrewriteaof
11
+ assert_equal 'Background append only file rewriting started', redis.bgrewriteaof
12
+ end
13
+
14
+ def test_bgsave
15
+ redis_cluster_mock(bgsave: ->(*_) { '+OK' }) do |redis|
16
+ assert_equal 'OK', redis.bgsave
17
+ end
18
+
19
+ err_msg = 'ERR An AOF log rewriting in progress: '\
20
+ "can't BGSAVE right now. "\
21
+ 'Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever possible.'
22
+
23
+ redis_cluster_mock(bgsave: ->(*_) { "-Error #{err_msg}" }) do |redis|
24
+ assert_raise(Redis::Cluster::CommandErrorCollection, 'Command error replied on any node') do
25
+ redis.bgsave
26
+ end
27
+ end
28
+ end
29
+
30
+ def test_client_kill
31
+ redis_cluster_mock(client: ->(*_) { '-Error ERR No such client' }) do |redis|
32
+ assert_raise(Redis::CommandError, 'ERR No such client') do
33
+ redis.client(:kill, '127.0.0.1:6379')
34
+ end
35
+ end
36
+
37
+ redis_cluster_mock(client: ->(*_) { '+OK' }) do |redis|
38
+ assert_equal 'OK', redis.client(:kill, '127.0.0.1:6379')
39
+ end
40
+ end
41
+
42
+ def test_client_list
43
+ a_client_info = redis.client(:list).first
44
+ actual = a_client_info.keys.sort
45
+ expected = %w[addr age cmd db events fd flags id idle multi name obl oll omem psub qbuf qbuf-free sub]
46
+ assert_equal expected, actual
47
+ end
48
+
49
+ def test_client_getname
50
+ redis.client(:setname, 'my-client-01')
51
+ assert_equal 'my-client-01', redis.client(:getname)
52
+ end
53
+
54
+ def test_client_pause
55
+ assert_equal 'OK', redis.client(:pause, 0)
56
+ end
57
+
58
+ def test_client_reply
59
+ target_version('3.2.0') do
60
+ assert_equal 'OK', redis.client(:reply, 'ON')
61
+ end
62
+ end
63
+
64
+ def test_client_setname
65
+ assert_equal 'OK', redis.client(:setname, 'my-client-01')
66
+ end
67
+
68
+ def test_command
69
+ assert_instance_of Array, redis.command
70
+ end
71
+
72
+ def test_command_count
73
+ assert_true(redis.command(:count) > 0)
74
+ end
75
+
76
+ def test_command_getkeys
77
+ assert_equal %w[a c e], redis.command(:getkeys, :mset, 'a', 'b', 'c', 'd', 'e', 'f')
78
+ end
79
+
80
+ def test_command_info
81
+ expected = [
82
+ ['get', 2, %w[readonly fast], 1, 1, 1],
83
+ ['set', -3, %w[write denyoom], 1, 1, 1],
84
+ ['eval', -3, %w[noscript movablekeys], 0, 0, 0]
85
+ ]
86
+ assert_equal expected, redis.command(:info, :get, :set, :eval)
87
+ end
88
+
89
+ def test_config_get
90
+ expected_keys = if version < '3.2.0'
91
+ %w[hash-max-ziplist-entries list-max-ziplist-entries set-max-intset-entries zset-max-ziplist-entries]
92
+ else
93
+ %w[hash-max-ziplist-entries set-max-intset-entries zset-max-ziplist-entries]
94
+ end
95
+
96
+ assert_equal expected_keys, redis.config(:get, '*max-*-entries*').keys.sort
97
+ end
98
+
99
+ def test_config_rewrite
100
+ redis_cluster_mock(config: ->(*_) { '-Error ERR Rewriting config file: Permission denied' }) do |redis|
101
+ assert_raise(Redis::Cluster::CommandErrorCollection, 'Command error replied on any node') do
102
+ redis.config(:rewrite)
103
+ end
104
+ end
105
+
106
+ redis_cluster_mock(config: ->(*_) { '+OK' }) do |redis|
107
+ assert_equal 'OK', redis.config(:rewrite)
108
+ end
109
+ end
110
+
111
+ def test_config_set
112
+ assert_equal 'OK', redis.config(:set, 'hash-max-ziplist-entries', 512)
113
+ end
114
+
115
+ def test_config_resetstat
116
+ assert_equal 'OK', redis.config(:resetstat)
117
+ end
118
+
119
+ def test_config_db_size
120
+ 10.times { |i| redis.set("key#{i}", 1) }
121
+ assert_equal 10, redis.dbsize
122
+ end
123
+
124
+ def test_debug_object
125
+ # DEBUG OBJECT is a debugging command that should not be used by clients.
126
+ end
127
+
128
+ def test_debug_segfault
129
+ # DEBUG SEGFAULT performs an invalid memory access that crashes Redis.
130
+ # It is used to simulate bugs during the development.
131
+ end
132
+
133
+ def test_flushall
134
+ assert_equal 'OK', redis.flushall
135
+ end
136
+
137
+ def test_flushdb
138
+ assert_equal 'OK', redis.flushdb
139
+ end
140
+
141
+ def test_info
142
+ assert_equal({ 'cluster_enabled' => '1' }, redis.info(:cluster))
143
+ end
144
+
145
+ def test_lastsave
146
+ assert_instance_of Array, redis.lastsave
147
+ end
148
+
149
+ def test_memory_doctor
150
+ target_version('4.0.0') do
151
+ assert_instance_of String, redis.memory(:doctor)
152
+ end
153
+ end
154
+
155
+ def test_memory_help
156
+ target_version('4.0.0') do
157
+ assert_instance_of Array, redis.memory(:help)
158
+ end
159
+ end
160
+
161
+ def test_memory_malloc_stats
162
+ target_version('4.0.0') do
163
+ assert_instance_of String, redis.memory('malloc-stats')
164
+ end
165
+ end
166
+
167
+ def test_memory_purge
168
+ target_version('4.0.0') do
169
+ assert_equal 'OK', redis.memory(:purge)
170
+ end
171
+ end
172
+
173
+ def test_memory_stats
174
+ target_version('4.0.0') do
175
+ assert_instance_of Array, redis.memory(:stats)
176
+ end
177
+ end
178
+
179
+ def test_memory_usage
180
+ target_version('4.0.0') do
181
+ redis.set('key1', 'Hello World')
182
+ assert_equal 61, redis.memory(:usage, 'key1')
183
+ end
184
+ end
185
+
186
+ def test_monitor
187
+ # Add MONITOR command test
188
+ end
189
+
190
+ def test_role
191
+ assert_equal %w[master master master], redis.role.map(&:first)
192
+ end
193
+
194
+ def test_save
195
+ assert_equal 'OK', redis.save
196
+ end
197
+
198
+ def test_shutdown
199
+ assert_raise(Redis::Cluster::OrchestrationCommandNotSupported, 'SHUTDOWN command should be...') do
200
+ redis.shutdown
201
+ end
202
+ end
203
+
204
+ def test_slaveof
205
+ assert_raise(Redis::CommandError, 'ERR SLAVEOF not allowed in cluster mode.') do
206
+ redis.slaveof(:no, :one)
207
+ end
208
+ end
209
+
210
+ def test_slowlog
211
+ assert_instance_of Array, redis.slowlog(:get, 1)
212
+ end
213
+
214
+ def test_sync
215
+ # Internal command used for replication
216
+ end
217
+
218
+ def test_time
219
+ assert_instance_of Array, redis.time
220
+ end
221
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require_relative 'lint/sets'
5
+
6
+ # ruby -w -Itest test/cluster_commands_on_sets_test.rb
7
+ # @see https://redis.io/commands#set
8
+ class TestClusterCommandsOnSets < Test::Unit::TestCase
9
+ include Helper::Cluster
10
+ include Lint::Sets
11
+
12
+ def test_sdiff
13
+ assert_raise(Redis::CommandError) { super }
14
+ end
15
+
16
+ def test_sdiffstore
17
+ assert_raise(Redis::CommandError) { super }
18
+ end
19
+
20
+ def test_sinter
21
+ assert_raise(Redis::CommandError) { super }
22
+ end
23
+
24
+ def test_sinterstore
25
+ assert_raise(Redis::CommandError) { super }
26
+ end
27
+
28
+ def test_smove
29
+ assert_raise(Redis::CommandError) { super }
30
+ end
31
+
32
+ def test_sunion
33
+ assert_raise(Redis::CommandError) { super }
34
+ end
35
+
36
+ def test_sunionstore
37
+ assert_raise(Redis::CommandError) { super }
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require_relative 'lint/sorted_sets'
5
+
6
+ # ruby -w -Itest test/cluster_commands_on_sorted_sets_test.rb
7
+ # @see https://redis.io/commands#sorted_set
8
+ class TestClusterCommandsOnSortedSets < Test::Unit::TestCase
9
+ include Helper::Cluster
10
+ include Lint::SortedSets
11
+
12
+ def test_zinterstore
13
+ assert_raise(Redis::CommandError) { super }
14
+ end
15
+
16
+ def test_zinterstore_with_aggregate
17
+ assert_raise(Redis::CommandError) { super }
18
+ end
19
+
20
+ def test_zinterstore_with_weights
21
+ assert_raise(Redis::CommandError) { super }
22
+ end
23
+
24
+ def test_zunionstore
25
+ assert_raise(Redis::CommandError) { super }
26
+ end
27
+
28
+ def test_zunionstore_with_aggregate
29
+ assert_raise(Redis::CommandError) { super }
30
+ end
31
+
32
+ def test_zunionstore_with_weights
33
+ assert_raise(Redis::CommandError) { super }
34
+ end
35
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ # ruby -w -Itest test/cluster_commands_on_streams_test.rb
6
+ # @see https://redis.io/commands#stream
7
+ class TestClusterCommandsOnStreams < Test::Unit::TestCase
8
+ include Helper::Cluster
9
+
10
+ MIN_REDIS_VERSION = '4.9.0'
11
+ ENTRY_ID_FORMAT = /\d+-\d+/
12
+
13
+ def setup
14
+ super
15
+ add_some_entries_to_streams_without_hashtag
16
+ add_some_entries_to_streams_with_hashtag
17
+ end
18
+
19
+ def add_some_entries_to_streams_without_hashtag
20
+ target_version(MIN_REDIS_VERSION) do
21
+ redis.xadd('stream1', '*', 'name', 'John', 'surname', 'Connor')
22
+ redis.xadd('stream1', '*', 'name', 'Sarah', 'surname', 'Connor')
23
+ redis.xadd('stream1', '*', 'name', 'Miles', 'surname', 'Dyson')
24
+ redis.xadd('stream1', '*', 'name', 'Peter', 'surname', 'Silberman')
25
+ end
26
+ end
27
+
28
+ def add_some_entries_to_streams_with_hashtag
29
+ target_version(MIN_REDIS_VERSION) do
30
+ redis.xadd('{stream}1', '*', 'name', 'John', 'surname', 'Connor')
31
+ redis.xadd('{stream}1', '*', 'name', 'Sarah', 'surname', 'Connor')
32
+ redis.xadd('{stream}1', '*', 'name', 'Miles', 'surname', 'Dyson')
33
+ redis.xadd('{stream}1', '*', 'name', 'Peter', 'surname', 'Silberman')
34
+ end
35
+ end
36
+
37
+ def assert_stream_entry(actual, expected_name, expected_surname)
38
+ actual_key = actual.keys.first
39
+ actual_values = actual[actual_key]
40
+
41
+ assert_match ENTRY_ID_FORMAT, actual_key
42
+ assert_equal expected_name, actual_values['name']
43
+ assert_equal expected_surname, actual_values['surname']
44
+ end
45
+
46
+ def assert_stream_pending(actual, expected_size_of_group, expected_consumer_name, expected_size_of_consumer)
47
+ assert_equal expected_size_of_group, actual[:size]
48
+ assert_match ENTRY_ID_FORMAT, actual[:min_entry_id]
49
+ assert_match ENTRY_ID_FORMAT, actual[:max_entry_id]
50
+ assert_equal({ expected_consumer_name => expected_size_of_consumer }, actual[:consumers])
51
+ end
52
+
53
+ # TODO: Remove this helper method when we implement streams interfaces
54
+ def hashify_stream_entries(reply)
55
+ reply.map do |entry_id, values|
56
+ [entry_id, Hash[values.each_slice(2).to_a]]
57
+ end.to_h
58
+ end
59
+
60
+ # TODO: Remove this helper method when we implement streams interfaces
61
+ def hashify_streams(reply)
62
+ reply.map do |stream_key, entries|
63
+ [stream_key, hashify_stream_entries(entries)]
64
+ end.to_h
65
+ end
66
+
67
+ # TODO: Remove this helper method when we implement streams interfaces
68
+ def hashify_stream_pendings(reply)
69
+ {
70
+ size: reply.first,
71
+ min_entry_id: reply[1],
72
+ max_entry_id: reply[2],
73
+ consumers: Hash[reply[3]]
74
+ }
75
+ end
76
+
77
+ def test_xadd
78
+ target_version(MIN_REDIS_VERSION) do
79
+ assert_match ENTRY_ID_FORMAT, redis.xadd('mystream', '*', 'type', 'T-800', 'model', '101')
80
+ assert_match ENTRY_ID_FORMAT, redis.xadd('my{stream}', '*', 'type', 'T-1000')
81
+ end
82
+ end
83
+
84
+ def test_xrange
85
+ target_version(MIN_REDIS_VERSION) do
86
+ actual = redis.xrange('stream1', '-', '+', 'COUNT', 1)
87
+ actual = hashify_stream_entries(actual) # TODO: Remove this step when we implement streams interfaces
88
+ assert_stream_entry(actual, 'John', 'Connor')
89
+
90
+ actual = redis.xrange('{stream}1', '-', '+', 'COUNT', 1)
91
+ actual = hashify_stream_entries(actual) # TODO: Remove this step when we implement streams interfaces
92
+ assert_stream_entry(actual, 'John', 'Connor')
93
+ end
94
+ end
95
+
96
+ def test_xrevrange
97
+ target_version(MIN_REDIS_VERSION) do
98
+ actual = redis.xrevrange('stream1', '+', '-', 'COUNT', 1)
99
+ actual = hashify_stream_entries(actual) # TODO: Remove this step when we implement streams interfaces
100
+ assert_stream_entry(actual, 'Peter', 'Silberman')
101
+
102
+ actual = redis.xrevrange('{stream}1', '+', '-', 'COUNT', 1)
103
+ actual = hashify_stream_entries(actual) # TODO: Remove this step when we implement streams interfaces
104
+ assert_stream_entry(actual, 'Peter', 'Silberman')
105
+ end
106
+ end
107
+
108
+ def test_xlen
109
+ target_version(MIN_REDIS_VERSION) do
110
+ assert_equal 4, redis.xlen('stream1')
111
+ assert_equal 4, redis.xlen('{stream}1')
112
+ end
113
+ end
114
+
115
+ def test_xread
116
+ target_version(MIN_REDIS_VERSION) do
117
+ # non blocking without hashtag
118
+ actual = redis.xread('COUNT', 1, 'STREAMS', 'stream1', 0)
119
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
120
+ assert_equal 'stream1', actual.keys.first
121
+ assert_stream_entry(actual['stream1'], 'John', 'Connor')
122
+
123
+ # blocking without hashtag
124
+ actual = redis.xread('COUNT', 1, 'BLOCK', 1, 'STREAMS', 'stream1', 0)
125
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
126
+ assert_equal 'stream1', actual.keys.first
127
+ assert_stream_entry(actual['stream1'], 'John', 'Connor')
128
+
129
+ # non blocking with hashtag
130
+ actual = redis.xread('COUNT', 1, 'STREAMS', '{stream}1', 0)
131
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
132
+ assert_equal '{stream}1', actual.keys.first
133
+ assert_stream_entry(actual['{stream}1'], 'John', 'Connor')
134
+
135
+ # blocking with hashtag
136
+ actual = redis.xread('COUNT', 1, 'BLOCK', 1, 'STREAMS', '{stream}1', 0)
137
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
138
+ assert_equal '{stream}1', actual.keys.first
139
+ assert_stream_entry(actual['{stream}1'], 'John', 'Connor')
140
+ end
141
+ end
142
+
143
+ def test_xreadgroup
144
+ target_version(MIN_REDIS_VERSION) do
145
+ # non blocking without hashtag
146
+ redis.xgroup('create', 'stream1', 'mygroup1', '$')
147
+ add_some_entries_to_streams_without_hashtag
148
+ actual = redis.xreadgroup('GROUP', 'mygroup1', 'T-1000', 'COUNT', 1, 'STREAMS', 'stream1', '>')
149
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
150
+ assert_equal 'stream1', actual.keys.first
151
+ assert_stream_entry(actual['stream1'], 'John', 'Connor')
152
+
153
+ # blocking without hashtag
154
+ redis.xgroup('create', 'stream1', 'mygroup2', '$')
155
+ add_some_entries_to_streams_without_hashtag
156
+ actual = redis.xreadgroup('GROUP', 'mygroup2', 'T-800', 'COUNT', 1, 'BLOCK', 1, 'STREAMS', 'stream1', '>')
157
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
158
+ assert_equal 'stream1', actual.keys.first
159
+ assert_stream_entry(actual['stream1'], 'John', 'Connor')
160
+
161
+ # non blocking with hashtag
162
+ redis.xgroup('create', '{stream}1', 'mygroup3', '$')
163
+ add_some_entries_to_streams_with_hashtag
164
+ actual = redis.xreadgroup('GROUP', 'mygroup3', 'T-1000', 'COUNT', 1, 'STREAMS', '{stream}1', '>')
165
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
166
+ assert_equal '{stream}1', actual.keys.first
167
+ assert_stream_entry(actual['{stream}1'], 'John', 'Connor')
168
+
169
+ # blocking with hashtag
170
+ redis.xgroup('create', '{stream}1', 'mygroup4', '$')
171
+ add_some_entries_to_streams_with_hashtag
172
+ actual = redis.xreadgroup('GROUP', 'mygroup4', 'T-800', 'COUNT', 1, 'BLOCK', 1, 'STREAMS', '{stream}1', '>')
173
+ actual = hashify_streams(actual) # TODO: Remove this step when we implement streams interfaces
174
+ assert_equal '{stream}1', actual.keys.first
175
+ assert_stream_entry(actual['{stream}1'], 'John', 'Connor')
176
+ end
177
+ end
178
+
179
+ def test_xpending
180
+ target_version(MIN_REDIS_VERSION) do
181
+ redis.xgroup('create', 'stream1', 'mygroup1', '$')
182
+ add_some_entries_to_streams_without_hashtag
183
+ redis.xreadgroup('GROUP', 'mygroup1', 'T-800', 'COUNT', 1, 'STREAMS', 'stream1', '>')
184
+ actual = redis.xpending('stream1', 'mygroup1')
185
+ actual = hashify_stream_pendings(actual) # TODO: Remove this step when we implement streams interfaces
186
+ assert_stream_pending(actual, 1, 'T-800', '1')
187
+
188
+ redis.xgroup('create', '{stream}1', 'mygroup2', '$')
189
+ add_some_entries_to_streams_with_hashtag
190
+ redis.xreadgroup('GROUP', 'mygroup2', 'T-800', 'COUNT', 1, 'STREAMS', '{stream}1', '>')
191
+ actual = redis.xpending('{stream}1', 'mygroup2')
192
+ actual = hashify_stream_pendings(actual) # TODO: Remove this step when we implement streams interfaces
193
+ assert_stream_pending(actual, 1, 'T-800', '1')
194
+ end
195
+ end
196
+ end