ably 0.8.14 → 0.8.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -61
- data/README.md +6 -0
- data/ably.gemspec +1 -1
- data/lib/ably/models/http_paginated_response.rb +90 -0
- data/lib/ably/models/paginated_result.rb +5 -0
- data/lib/ably/modules/event_emitter.rb +30 -15
- data/lib/ably/modules/model_common.rb +25 -0
- data/lib/ably/realtime/channel/channel_manager.rb +3 -3
- data/lib/ably/realtime/client.rb +13 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +2 -2
- data/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/ably/rest/client.rb +60 -3
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/auth_spec.rb +29 -0
- data/spec/acceptance/realtime/channel_spec.rb +8 -6
- data/spec/acceptance/realtime/client_spec.rb +52 -0
- data/spec/acceptance/realtime/connection_spec.rb +44 -8
- data/spec/acceptance/realtime/presence_spec.rb +79 -38
- data/spec/acceptance/rest/channel_spec.rb +2 -4
- data/spec/acceptance/rest/client_spec.rb +69 -21
- data/spec/acceptance/rest/presence_spec.rb +10 -12
- data/spec/unit/models/http_paginated_result_spec.rb +380 -0
- data/spec/unit/modules/event_emitter_spec.rb +109 -51
- data/spec/unit/realtime/presence_spec.rb +3 -3
- metadata +10 -8
@@ -32,6 +32,11 @@ module Ably::Models
|
|
32
32
|
@make_async = options.fetch(:async_blocking_operations, false)
|
33
33
|
|
34
34
|
@items = http_response.body
|
35
|
+
if @items.nil? || @items.to_s.strip.empty?
|
36
|
+
@items = []
|
37
|
+
end
|
38
|
+
@items = [@items] if @items.kind_of?(Hash)
|
39
|
+
|
35
40
|
@items = coerce_items_into(items, @coerce_into) if @coerce_into
|
36
41
|
@items = items.map { |item| yield item } if block_given?
|
37
42
|
end
|
@@ -50,18 +50,14 @@ module Ably
|
|
50
50
|
#
|
51
51
|
# @return [void]
|
52
52
|
def on(*event_names, &block)
|
53
|
-
event_names
|
54
|
-
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block)
|
55
|
-
end
|
53
|
+
add_callback event_names, proc_for_block(block)
|
56
54
|
end
|
57
55
|
|
58
56
|
# Equivalent of {#on} but any exception raised in a block will bubble up and cause this client library to fail.
|
59
57
|
# This method should only be used internally by the client library.
|
60
58
|
# @api private
|
61
59
|
def unsafe_on(*event_names, &block)
|
62
|
-
event_names
|
63
|
-
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, unsafe: true)
|
64
|
-
end
|
60
|
+
add_callback event_names, proc_for_block(block, unsafe: true)
|
65
61
|
end
|
66
62
|
|
67
63
|
# On receiving an event maching the event_name, call the provided block only once and remove the registered callback
|
@@ -70,24 +66,20 @@ module Ably
|
|
70
66
|
#
|
71
67
|
# @return [void]
|
72
68
|
def once(*event_names, &block)
|
73
|
-
event_names
|
74
|
-
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true)
|
75
|
-
end
|
69
|
+
add_callback event_names, proc_for_block(block, delete_once_run: true)
|
76
70
|
end
|
77
71
|
|
78
72
|
# Equivalent of {#once} but any exception raised in a block will bubble up and cause this client library to fail.
|
79
73
|
# This method should only be used internally by the client library.
|
80
74
|
# @api private
|
81
75
|
def unsafe_once(*event_names, &block)
|
82
|
-
event_names
|
83
|
-
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true, unsafe: true)
|
84
|
-
end
|
76
|
+
add_callback event_names, proc_for_block(block, delete_once_run: true, unsafe: true)
|
85
77
|
end
|
86
78
|
|
87
79
|
# Emit an event with event_name that will in turn call all matching callbacks setup with `on`
|
88
80
|
def emit(event_name, *args)
|
89
|
-
callbacks[callbacks_event_coerced(event_name)].
|
90
|
-
clone.
|
81
|
+
[callbacks_any, callbacks[callbacks_event_coerced(event_name)]].each do |callback_arr|
|
82
|
+
callback_arr.clone.
|
91
83
|
select do |proc_hash|
|
92
84
|
if proc_hash[:unsafe]
|
93
85
|
proc_hash[:emit_proc].call(*args)
|
@@ -95,8 +87,9 @@ module Ably
|
|
95
87
|
safe_yield proc_hash[:emit_proc], *args
|
96
88
|
end
|
97
89
|
end.each do |callback|
|
98
|
-
|
90
|
+
callback_arr.delete callback
|
99
91
|
end
|
92
|
+
end
|
100
93
|
end
|
101
94
|
|
102
95
|
# Remove all callbacks for event_name.
|
@@ -121,6 +114,14 @@ module Ably
|
|
121
114
|
callbacks[callbacks_event_coerced(event_name)].clear
|
122
115
|
end
|
123
116
|
end
|
117
|
+
|
118
|
+
if event_names.empty?
|
119
|
+
if block_given?
|
120
|
+
callbacks_any.delete_if { |proc_hash| proc_hash[:block] == block }
|
121
|
+
else
|
122
|
+
callbacks_any.clear
|
123
|
+
end
|
124
|
+
end
|
124
125
|
end
|
125
126
|
|
126
127
|
private
|
@@ -128,6 +129,16 @@ module Ably
|
|
128
129
|
klass.extend ClassMethods
|
129
130
|
end
|
130
131
|
|
132
|
+
def add_callback(event_names, proc_block)
|
133
|
+
if event_names.empty?
|
134
|
+
callbacks_any << proc_block
|
135
|
+
else
|
136
|
+
event_names.each do |event_name|
|
137
|
+
callbacks[callbacks_event_coerced(event_name)] << proc_block
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
131
142
|
# Create a Hash with a proc that calls the provided block and returns true if option :delete_once_run is set to true.
|
132
143
|
# #emit automatically deletes any blocks that return true thus allowing a block to be run once
|
133
144
|
def proc_for_block(block, options = {})
|
@@ -145,6 +156,10 @@ module Ably
|
|
145
156
|
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
146
157
|
end
|
147
158
|
|
159
|
+
def callbacks_any
|
160
|
+
@callbacks_any ||= []
|
161
|
+
end
|
162
|
+
|
148
163
|
def callbacks_event_coerced(event_name)
|
149
164
|
if self.class.event_emitter_coerce_proc
|
150
165
|
self.class.event_emitter_coerce_proc.call(event_name)
|
@@ -32,6 +32,14 @@ module Ably::Modules
|
|
32
32
|
as_json.to_json(*args)
|
33
33
|
end
|
34
34
|
|
35
|
+
# Like to_json but encodes all binary fields to hex
|
36
|
+
def to_safe_json(*args)
|
37
|
+
as_json.
|
38
|
+
each_with_object({}) do |(key, val), obj|
|
39
|
+
obj[key] = to_safe_jsonable_val(val)
|
40
|
+
end.to_json(*args)
|
41
|
+
end
|
42
|
+
|
35
43
|
# @!attribute [r] hash
|
36
44
|
# @return [Integer] Compute a hash-code for this hash. Two hashes with the same content will have the same hash code
|
37
45
|
def hash
|
@@ -39,6 +47,23 @@ module Ably::Modules
|
|
39
47
|
end
|
40
48
|
|
41
49
|
private
|
50
|
+
def to_safe_jsonable_val(val)
|
51
|
+
case val
|
52
|
+
when Array
|
53
|
+
val.map { |array_val| to_safe_jsonable_val(array_val) }
|
54
|
+
when Hash
|
55
|
+
val.each_with_object({}) { |(key, hash_val), obj| obj[key] = to_safe_jsonable_val(hash_val) }
|
56
|
+
when String
|
57
|
+
if val.encoding == Encoding::ASCII_8BIT
|
58
|
+
val.unpack("H*").first
|
59
|
+
else
|
60
|
+
val
|
61
|
+
end
|
62
|
+
else
|
63
|
+
val
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
42
67
|
def ensure_utf8_string_for(attribute, value)
|
43
68
|
if value
|
44
69
|
raise ArgumentError, "#{attribute} must be a String" unless value.kind_of?(String)
|
@@ -58,7 +58,7 @@ module Ably::Realtime
|
|
58
58
|
# all messages awaiting an ACK response should fail immediately
|
59
59
|
def fail_messages_awaiting_ack(error)
|
60
60
|
# Allow a short time for other queued operations to complete before failing all messages
|
61
|
-
EventMachine.
|
61
|
+
EventMachine.next_tick do
|
62
62
|
error = Ably::Exceptions::MessageDeliveryFailed.new("Channel cannot publish messages whilst state is '#{channel.state}'") unless error
|
63
63
|
fail_messages_in_queue connection.__pending_message_ack_queue__, error
|
64
64
|
fail_messages_in_queue connection.__outgoing_message_queue__, error
|
@@ -76,10 +76,10 @@ module Ably::Realtime
|
|
76
76
|
|
77
77
|
def nack_messages(protocol_message, error)
|
78
78
|
(protocol_message.messages + protocol_message.presence).each do |message|
|
79
|
-
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.
|
79
|
+
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_safe_json}, protocol message: #{protocol_message}"
|
80
80
|
message.fail error
|
81
81
|
end
|
82
|
-
logger.debug "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.
|
82
|
+
logger.debug "Calling NACK failure callbacks for #{protocol_message.class.name} - #{protocol_message.to_safe_json}"
|
83
83
|
protocol_message.fail error
|
84
84
|
end
|
85
85
|
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -68,6 +68,9 @@ module Ably
|
|
68
68
|
#
|
69
69
|
# @param (see Ably::Rest::Client#initialize)
|
70
70
|
# @option options (see Ably::Rest::Client#initialize)
|
71
|
+
# @option options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
|
72
|
+
# Whilst the proc is called synchronously, it does not block the EventMachine reactor as it is run in a separate thread.
|
73
|
+
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
71
74
|
# @option options [Boolean] :queue_messages If false, this disables the default behaviour whereby the library queues messages on a connection in the disconnected or connecting states
|
72
75
|
# @option options [Boolean] :echo_messages If false, prevents messages originating from this connection being echoed back on the same connection
|
73
76
|
# @option options [String] :recover When a recover option is specified a connection inherits the state of a previous connection that may have existed under a different instance of the Realtime library, please refer to the API documentation for further information on connection state recovery
|
@@ -144,6 +147,16 @@ module Ably
|
|
144
147
|
connection.connect(&block)
|
145
148
|
end
|
146
149
|
|
150
|
+
# (see Ably::Rest::Client#request)
|
151
|
+
# @yield [Ably::Models::HttpPaginatedResponse<>] An Array of Stats
|
152
|
+
#
|
153
|
+
# @return [Ably::Util::SafeDeferrable]
|
154
|
+
def request(method, path, params = {}, body = nil, headers = {}, &callback)
|
155
|
+
async_wrap(callback) do
|
156
|
+
rest_client.request(method, path, params, body, headers, async_blocking_operations: true)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
147
160
|
# @!attribute [r] endpoint
|
148
161
|
# @return [URI::Generic] Default Ably Realtime endpoint used for all requests
|
149
162
|
def endpoint
|
@@ -178,14 +178,14 @@ module Ably::Realtime
|
|
178
178
|
|
179
179
|
def ack_messages(messages)
|
180
180
|
messages.each do |message|
|
181
|
-
logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.
|
181
|
+
logger.debug "Calling ACK success callbacks for #{message.class.name} - #{message.to_safe_json}"
|
182
182
|
message.succeed message
|
183
183
|
end
|
184
184
|
end
|
185
185
|
|
186
186
|
def nack_messages(messages, protocol_message)
|
187
187
|
messages.each do |message|
|
188
|
-
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.
|
188
|
+
logger.debug "Calling NACK failure callbacks for #{message.class.name} - #{message.to_safe_json}, protocol message: #{protocol_message}"
|
189
189
|
message.fail protocol_message.error
|
190
190
|
end
|
191
191
|
end
|
@@ -200,7 +200,7 @@ module Ably::Realtime
|
|
200
200
|
return unless ensure_presence_message_is_valid(presence_message)
|
201
201
|
|
202
202
|
unless should_update_member?(presence_message)
|
203
|
-
logger.debug "#{self.class.name}: Skipped presence member #{presence_message.action} on channel #{presence.channel.name}.\n#{presence_message.
|
203
|
+
logger.debug "#{self.class.name}: Skipped presence member #{presence_message.action} on channel #{presence.channel.name}.\n#{presence_message.to_safe_json}"
|
204
204
|
return
|
205
205
|
end
|
206
206
|
|
@@ -239,13 +239,13 @@ module Ably::Realtime
|
|
239
239
|
end
|
240
240
|
|
241
241
|
def add_presence_member(presence_message)
|
242
|
-
logger.debug "#{self.class.name}: Member '#{presence_message.member_key}' for event '#{presence_message.action}' #{members.has_key?(presence_message.member_key) ? 'updated' : 'added'}.\n#{presence_message.
|
242
|
+
logger.debug "#{self.class.name}: Member '#{presence_message.member_key}' for event '#{presence_message.action}' #{members.has_key?(presence_message.member_key) ? 'updated' : 'added'}.\n#{presence_message.to_safe_json}"
|
243
243
|
members[presence_message.member_key] = { present: true, message: presence_message }
|
244
244
|
presence.emit_message presence_message.action, presence_message
|
245
245
|
end
|
246
246
|
|
247
247
|
def remove_presence_member(presence_message)
|
248
|
-
logger.debug "#{self.class.name}: Member '#{presence_message.member_key}' removed.\n#{presence_message.
|
248
|
+
logger.debug "#{self.class.name}: Member '#{presence_message.member_key}' removed.\n#{presence_message.to_safe_json}"
|
249
249
|
|
250
250
|
if in_sync?
|
251
251
|
members.delete presence_message.member_key
|
data/lib/ably/rest/client.rb
CHANGED
@@ -251,15 +251,67 @@ module Ably
|
|
251
251
|
# Perform an HTTP GET request to the API using configured authentication
|
252
252
|
#
|
253
253
|
# @return [Faraday::Response]
|
254
|
+
#
|
255
|
+
# @api private
|
254
256
|
def get(path, params = {}, options = {})
|
255
|
-
|
257
|
+
raw_request(:get, path, params, options)
|
256
258
|
end
|
257
259
|
|
258
260
|
# Perform an HTTP POST request to the API using configured authentication
|
259
261
|
#
|
260
262
|
# @return [Faraday::Response]
|
263
|
+
#
|
264
|
+
# @api private
|
261
265
|
def post(path, params, options = {})
|
262
|
-
|
266
|
+
raw_request(:post, path, params, options)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Perform an HTTP request to the Ably API
|
270
|
+
# This is a convenience for customers who wish to use bleeding edge REST API functionality
|
271
|
+
# that is either not documented or is not included in the API for our client libraries.
|
272
|
+
# The REST client library provides a function to issue HTTP requests to the Ably endpoints
|
273
|
+
# with all the built in functionality of the library such as authentication, paging,
|
274
|
+
# fallback hosts, MsgPack and JSON support etc.
|
275
|
+
#
|
276
|
+
# @param method [Symbol] The HTTP method symbol such as +:get+, +:post+, +:put+
|
277
|
+
# @param path [String] The path of the URL such +/channel/foo/publish+
|
278
|
+
# @param params [Hash, nil] Optional querystring params
|
279
|
+
# @param body [Hash, nil] Optional body for the POST or PUT request, must be nil or a JSON-like object
|
280
|
+
# @param headers [Hash, nil] Optional additional headers
|
281
|
+
#
|
282
|
+
# @return [Ably::Models::HttpPaginatedResponse<>]
|
283
|
+
def request(method, path, params = {}, body = nil, headers = {}, options = {})
|
284
|
+
raise "Method #{method.to_s.upcase} not supported" unless [:get, :put, :post].include?(method.to_sym)
|
285
|
+
|
286
|
+
response = case method.to_sym
|
287
|
+
when :get
|
288
|
+
reauthorise_on_authorisation_failure do
|
289
|
+
send_request(method, path, params, headers: headers)
|
290
|
+
end
|
291
|
+
when :post
|
292
|
+
path_with_params = Addressable::URI.new
|
293
|
+
path_with_params.query_values = params || {}
|
294
|
+
query = path_with_params.query
|
295
|
+
reauthorise_on_authorisation_failure do
|
296
|
+
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
paginated_options = {
|
301
|
+
async_blocking_operations: options.delete(:async_blocking_operations),
|
302
|
+
}
|
303
|
+
|
304
|
+
Ably::Models::HttpPaginatedResponse.new(response, path, self, paginated_options)
|
305
|
+
|
306
|
+
rescue Exceptions::ResourceMissing, Exceptions::ForbiddenRequest, Exceptions::ResourceMissing => e
|
307
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
308
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
309
|
+
rescue Exceptions::TokenExpired, Exceptions::UnauthorizedRequest => e
|
310
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
311
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
312
|
+
rescue Exceptions::InvalidRequest, Exceptions::ServerError => e
|
313
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
314
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
263
315
|
end
|
264
316
|
|
265
317
|
# @!attribute [r] endpoint
|
@@ -360,7 +412,7 @@ module Ably
|
|
360
412
|
end
|
361
413
|
|
362
414
|
private
|
363
|
-
def
|
415
|
+
def raw_request(method, path, params = {}, options = {})
|
364
416
|
options = options.clone
|
365
417
|
if options.delete(:disable_automatic_reauthorise) == true
|
366
418
|
send_request(method, path, params, options)
|
@@ -385,6 +437,11 @@ module Ably
|
|
385
437
|
connection(use_fallback: use_fallback).send(method, path, params) do |request|
|
386
438
|
unless options[:send_auth_header] == false
|
387
439
|
request.headers[:authorization] = auth.auth_header
|
440
|
+
if options[:headers]
|
441
|
+
options[:headers].map do |key, val|
|
442
|
+
request.headers[key] = val
|
443
|
+
end
|
444
|
+
end
|
388
445
|
end
|
389
446
|
end
|
390
447
|
|
data/lib/ably/version.rb
CHANGED
@@ -186,6 +186,35 @@ describe Ably::Realtime::Auth, :event_machine do
|
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
189
|
+
context 'with auth_callback blocking' do
|
190
|
+
let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
|
191
|
+
let(:client_options) { default_options.merge(auth_callback: auth_callback) }
|
192
|
+
let(:pause) { 5 }
|
193
|
+
|
194
|
+
context 'with a slow auth callback response' do
|
195
|
+
let(:auth_callback) do
|
196
|
+
Proc.new do
|
197
|
+
sleep pause
|
198
|
+
rest_auth_client.auth.request_token
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'asynchronously authenticates' do
|
203
|
+
timers_called = 0
|
204
|
+
block = Proc.new do
|
205
|
+
timers_called += 1
|
206
|
+
EventMachine.add_timer(0.5, &block)
|
207
|
+
end
|
208
|
+
block.call
|
209
|
+
client.connect
|
210
|
+
client.connection.on(:connected) do
|
211
|
+
expect(timers_called).to be >= (pause-1) / 0.5
|
212
|
+
stop_reactor
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
189
218
|
context 'when implicitly called, with an explicit ClientOptions client_id' do
|
190
219
|
let(:client_id) { random_str }
|
191
220
|
let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id, log_level: :none) }
|
@@ -92,12 +92,14 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
92
92
|
|
93
93
|
it 'reattaches' do
|
94
94
|
channel.attach do
|
95
|
-
channel.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
channel.once(:failed) do
|
96
|
+
expect(channel).to be_failed
|
97
|
+
channel.attach do
|
98
|
+
expect(channel).to be_attached
|
99
|
+
stop_reactor
|
100
|
+
end
|
100
101
|
end
|
102
|
+
channel.transition_state_machine :failed, reason: RuntimeError.new
|
101
103
|
end
|
102
104
|
end
|
103
105
|
end
|
@@ -371,7 +373,7 @@ describe Ably::Realtime::Channel, :event_machine do
|
|
371
373
|
expect(message_id.uniq.count).to eql(1)
|
372
374
|
|
373
375
|
# Check that messages use index 0,1,2 in the ID
|
374
|
-
message_indexes = messages.map { |msg| msg.id.split(':')
|
376
|
+
message_indexes = messages.map { |msg| msg.id.split(':').last }
|
375
377
|
expect(message_indexes).to include("0", "1", "2")
|
376
378
|
stop_reactor
|
377
379
|
end
|
@@ -229,5 +229,57 @@ describe Ably::Realtime::Client, :event_machine do
|
|
229
229
|
stop_reactor
|
230
230
|
end
|
231
231
|
end
|
232
|
+
|
233
|
+
context '#request (#RSC19*)' do
|
234
|
+
let(:client_options) { default_options.merge(key: api_key) }
|
235
|
+
|
236
|
+
context 'get' do
|
237
|
+
it 'returns an HttpPaginatedResponse object' do
|
238
|
+
subject.request(:get, 'time').callback do |response|
|
239
|
+
expect(response).to be_a(Ably::Models::HttpPaginatedResponse)
|
240
|
+
expect(response.status_code).to eql(200)
|
241
|
+
stop_reactor
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context '404 request to invalid URL' do
|
246
|
+
it 'returns an object with 404 status code and error message' do
|
247
|
+
subject.request(:get, 'does-not-exist').callback do |response|
|
248
|
+
expect(response).to be_a(Ably::Models::HttpPaginatedResponse)
|
249
|
+
expect(response.error_message).to match(/Could not find/)
|
250
|
+
expect(response.error_code).to eql(40400)
|
251
|
+
expect(response.status_code).to eql(404)
|
252
|
+
stop_reactor
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'paged results' do
|
258
|
+
let(:channel_name) { random_str }
|
259
|
+
|
260
|
+
it 'provides paging' do
|
261
|
+
10.times do
|
262
|
+
subject.rest_client.request(:post, "/channels/#{channel_name}/publish", {}, { 'name': 'test' })
|
263
|
+
end
|
264
|
+
|
265
|
+
subject.request(:get, "/channels/#{channel_name}/messages", { limit: 2 }).callback do |response|
|
266
|
+
expect(response.items.length).to eql(2)
|
267
|
+
expect(response).to be_has_next
|
268
|
+
response.next do |next_page|
|
269
|
+
expect(next_page.items.length).to eql(2)
|
270
|
+
expect(next_page).to be_has_next
|
271
|
+
first_page_ids = response.items.map { |message| message['id'] }.uniq.sort
|
272
|
+
next_page_ids = next_page.items.map { |message| message['id'] }.uniq.sort
|
273
|
+
expect(first_page_ids).to_not eql(next_page_ids)
|
274
|
+
next_page.next do |third_page|
|
275
|
+
expect(third_page.items.length).to eql(2)
|
276
|
+
stop_reactor
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
232
284
|
end
|
233
285
|
end
|