ably 1.1.7 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +1 -1
  3. data/CHANGELOG.md +99 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +2 -2
  6. data/SPEC.md +0 -7
  7. data/UPDATING.md +30 -0
  8. data/ably.gemspec +11 -24
  9. data/lib/ably/auth.rb +8 -8
  10. data/lib/ably/logger.rb +4 -4
  11. data/lib/ably/models/channel_options.rb +97 -0
  12. data/lib/ably/models/connection_details.rb +8 -2
  13. data/lib/ably/models/delta_extras.rb +29 -0
  14. data/lib/ably/models/device_details.rb +1 -1
  15. data/lib/ably/models/error_info.rb +6 -2
  16. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -0
  17. data/lib/ably/models/message.rb +14 -3
  18. data/lib/ably/models/protocol_message.rb +23 -14
  19. data/lib/ably/models/token_details.rb +7 -2
  20. data/lib/ably/models/token_request.rb +1 -1
  21. data/lib/ably/modules/ably.rb +1 -1
  22. data/lib/ably/modules/channels_collection.rb +22 -2
  23. data/lib/ably/modules/conversions.rb +34 -0
  24. data/lib/ably/realtime/auth.rb +2 -2
  25. data/lib/ably/realtime/channel/channel_manager.rb +16 -4
  26. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
  27. data/lib/ably/realtime/channel/publisher.rb +3 -2
  28. data/lib/ably/realtime/channel.rb +54 -22
  29. data/lib/ably/realtime/channels.rb +1 -1
  30. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  31. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  32. data/lib/ably/realtime/connection.rb +0 -3
  33. data/lib/ably/rest/channel.rb +28 -35
  34. data/lib/ably/rest/client.rb +23 -8
  35. data/lib/ably/rest/middleware/encoder.rb +1 -1
  36. data/lib/ably/rest/middleware/exceptions.rb +1 -1
  37. data/lib/ably/rest/middleware/external_exceptions.rb +1 -1
  38. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +1 -1
  39. data/lib/ably/rest/middleware/logger.rb +1 -1
  40. data/lib/ably/rest/middleware/parse_json.rb +1 -1
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +1 -1
  42. data/lib/ably/util/crypto.rb +1 -1
  43. data/lib/ably/version.rb +2 -2
  44. data/spec/acceptance/realtime/channel_spec.rb +458 -27
  45. data/spec/acceptance/realtime/channels_spec.rb +59 -7
  46. data/spec/acceptance/realtime/connection_failures_spec.rb +56 -1
  47. data/spec/acceptance/realtime/connection_spec.rb +270 -1
  48. data/spec/acceptance/realtime/message_spec.rb +77 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +18 -1
  50. data/spec/acceptance/rest/auth_spec.rb +18 -0
  51. data/spec/acceptance/rest/channel_spec.rb +73 -11
  52. data/spec/acceptance/rest/channels_spec.rb +23 -6
  53. data/spec/acceptance/rest/client_spec.rb +3 -3
  54. data/spec/acceptance/rest/message_spec.rb +61 -3
  55. data/spec/lib/unit/models/channel_options_spec.rb +52 -0
  56. data/spec/run_parallel_tests +2 -7
  57. data/spec/support/test_app.rb +1 -1
  58. data/spec/unit/logger_spec.rb +6 -14
  59. data/spec/unit/models/delta_extras_spec.rb +14 -0
  60. data/spec/unit/models/error_info_spec.rb +17 -1
  61. data/spec/unit/models/message_spec.rb +38 -0
  62. data/spec/unit/models/protocol_message_spec.rb +77 -27
  63. data/spec/unit/models/token_details_spec.rb +14 -0
  64. data/spec/unit/realtime/channel_spec.rb +2 -1
  65. data/spec/unit/realtime/channels_spec.rb +53 -15
  66. data/spec/unit/rest/channel_spec.rb +40 -7
  67. data/spec/unit/rest/channels_spec.rb +81 -14
  68. data/spec/unit/rest/client_spec.rb +27 -0
  69. metadata +46 -11
@@ -1081,7 +1081,7 @@ describe Ably::Rest::Client do
1081
1081
  end
1082
1082
 
1083
1083
  context 'version headers', :webmock do
1084
- [nil, 'ably-ruby/1.1.1 ruby/1.9.3'].each do |agent|
1084
+ [nil, 'ably-ruby/1.1.1 ruby/3.1.1'].each do |agent|
1085
1085
  context "with #{agent ? "custom #{agent}" : 'default'} agent" do
1086
1086
  let(:client_options) { default_options.merge(key: api_key, agent: agent) }
1087
1087
 
@@ -1097,7 +1097,7 @@ describe Ably::Rest::Client do
1097
1097
  it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
1098
1098
  client.channels.get('foo').publish("event")
1099
1099
  expect(publish_message_stub).to have_been_requested
1100
- expect(Ably::PROTOCOL_VERSION).to eql('1.1')
1100
+ expect(Ably::PROTOCOL_VERSION).to eql('1.2')
1101
1101
  end
1102
1102
  end
1103
1103
  end
@@ -1231,7 +1231,7 @@ describe Ably::Rest::Client do
1231
1231
  end
1232
1232
  end
1233
1233
 
1234
- context 'request_id generation' do
1234
+ context 'request_id generation (#RSC7c)' do
1235
1235
  context 'Timeout error' do
1236
1236
  context 'with option add_request_ids: true and no fallback hosts', :webmock, :prevent_log_stubbing do
1237
1237
  let(:custom_logger_object) { TestLogger.new }
@@ -24,6 +24,57 @@ describe Ably::Rest::Channel, 'messages' do
24
24
  end
25
25
  end
26
26
 
27
+ context 'a single Message object (#RSL1a)' do
28
+ let(:name) { random_str }
29
+ let(:data) { random_str }
30
+ let(:message) { Ably::Models::Message.new(name: name, data: data) }
31
+
32
+ it 'publishes the message' do
33
+ channel.publish(message)
34
+ expect(channel.history.items.length).to eql(1)
35
+ message = channel.history.items.first
36
+ expect(message.name).to eq(name)
37
+ expect(message.data).to eq(data)
38
+ end
39
+ end
40
+
41
+ context 'an array of Message objects (#RSL1a)' do
42
+ let(:data) { random_str }
43
+ let(:message1) { Ably::Models::Message.new(name: random_str, data: data) }
44
+ let(:message2) { Ably::Models::Message.new(name: random_str, data: data) }
45
+ let(:message3) { Ably::Models::Message.new(name: random_str, data: data) }
46
+
47
+ it 'publishes three messages' do
48
+ channel.publish([message1, message2, message3])
49
+ expect(channel.history.items.length).to eql(3)
50
+ end
51
+ end
52
+
53
+ context 'an array of hashes (#RSL1a)' do
54
+ let(:data) { random_str }
55
+ let(:message1) { { name: random_str, data: data } }
56
+ let(:message2) { { name: random_str, data: data } }
57
+ let(:message3) { { name: random_str, data: data } }
58
+
59
+ it 'publishes three messages' do
60
+ channel.publish([message1, message2, message3])
61
+ expect(channel.history.items.length).to eql(3)
62
+ end
63
+ end
64
+
65
+ context 'a name with data payload (#RSL1a, #RSL1b)' do
66
+ let(:name) { random_str }
67
+ let(:data) { random_str }
68
+
69
+ it 'publishes the message' do
70
+ channel.publish(name, data)
71
+ expect(channel.history.items.length).to eql(1)
72
+ message = channel.history.items.first
73
+ expect(message.name).to eq(name)
74
+ expect(message.data).to eq(data)
75
+ end
76
+ end
77
+
27
78
  context 'with supported data payload content type' do
28
79
  context 'JSON Object (Hash)' do
29
80
  let(:data) { { 'Hash' => 'true' } }
@@ -153,13 +204,20 @@ describe Ably::Rest::Channel, 'messages' do
153
204
  end
154
205
  end
155
206
 
156
- specify 'idempotent publishing is disabled by default with 1.1 (#TO3n)' do
207
+ specify 'idempotent publishing is disabled by default with <= 1.1 (#TO3n)' do
208
+ stub_const 'Ably::PROTOCOL_VERSION', '1.0'
209
+ client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
210
+ expect(client.idempotent_rest_publishing).to be_falsey
211
+ stub_const 'Ably::PROTOCOL_VERSION', '1.1'
157
212
  client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
158
213
  expect(client.idempotent_rest_publishing).to be_falsey
159
214
  end
160
215
 
161
- specify 'idempotent publishing is enabled by default with 1.2 (#TO3n)' do
162
- stub_const 'Ably::VERSION', '1.2.0'
216
+ specify 'idempotent publishing is enabled by default with >= 1.2 (#TO3n)' do
217
+ stub_const 'Ably::PROTOCOL_VERSION', '1.2'
218
+ client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
219
+ expect(client.idempotent_rest_publishing).to be_truthy
220
+ stub_const 'Ably::PROTOCOL_VERSION', '1.3'
163
221
  client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
164
222
  expect(client.idempotent_rest_publishing).to be_truthy
165
223
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Ably::Models::ChannelOptions do
6
+ let(:modes) { nil }
7
+ let(:params) { {} }
8
+ let(:options) { described_class.new(modes: modes, params: params) }
9
+
10
+ describe '#modes_to_flags' do
11
+ let(:modes) { %w[publish subscribe presence_subscribe] }
12
+
13
+ subject(:protocol_message) do
14
+ Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Attach, flags: options.modes_to_flags)
15
+ end
16
+
17
+ it 'converts modes to ProtocolMessage#flags correctly' do
18
+ expect(protocol_message.has_attach_publish_flag?).to eq(true)
19
+ expect(protocol_message.has_attach_subscribe_flag?).to eq(true)
20
+ expect(protocol_message.has_attach_presence_subscribe_flag?).to eq(true)
21
+
22
+ expect(protocol_message.has_attach_resume_flag?).to eq(false)
23
+ expect(protocol_message.has_attach_presence_flag?).to eq(false)
24
+ end
25
+ end
26
+
27
+ describe '#set_modes_from_flags' do
28
+ let(:subscribe_flag) { 262144 }
29
+
30
+ it 'converts flags to ChannelOptions#modes correctly' do
31
+ result = options.set_modes_from_flags(subscribe_flag)
32
+
33
+ expect(result).to eq(options.modes)
34
+ expect(options.modes.map(&:to_sym)).to eq(%i[subscribe])
35
+ end
36
+ end
37
+
38
+ describe '#set_params' do
39
+ let(:previous_params) { { example_attribute: 1 } }
40
+ let(:new_params) { { new_attribute: 1 } }
41
+ let(:params) { previous_params }
42
+
43
+ it 'should be able to overwrite attributes' do
44
+ expect { options.set_params(new_params) }.to \
45
+ change { options.params }.from(previous_params).to(new_params)
46
+ end
47
+
48
+ it 'should be able to make params empty' do # (1)
49
+ expect { options.set_params({}) }.to change { options.params }.from(previous_params).to({})
50
+ end
51
+ end
52
+ end
@@ -11,13 +11,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
11
11
  bundle exec rspec "${DIR}/unit"
12
12
  unit_status=$?
13
13
 
14
- if ruby -v | grep -e "1.9"; then
15
- # Output with test ID is not supported with this old version of RSpec
16
- # So it will be jumbled sadly for 1.9.*
17
- bundle exec parallel_rspec "${DIR}/acceptance"
18
- else
19
- bundle exec parallel_rspec "${DIR}/acceptance" --prefix-output-with-test-env-number
20
- fi
14
+ bundle exec parallel_rspec "${DIR}/acceptance" --prefix-output-with-test-env-number
15
+
21
16
  acceptance_status=$?
22
17
 
23
18
  if [ $unit_status -ne 0 ]; then
@@ -59,7 +59,7 @@ class TestApp
59
59
 
60
60
  url = "#{sandbox_client.endpoint}/apps/#{app_id}"
61
61
 
62
- basic_auth = Base64.encode64(api_key).chomp
62
+ basic_auth = Base64.urlsafe_encode64(api_key).chomp
63
63
  headers = { "Authorization" => "Basic #{basic_auth}" }
64
64
 
65
65
  Faraday.delete(url, nil, headers)
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Ably::Logger do
3
+ describe Ably::Logger, :prevent_log_stubbing do
4
4
  let(:rest_client) do
5
5
  instance_double('Ably::Rest::Client')
6
6
  end
@@ -18,14 +18,10 @@ describe Ably::Logger do
18
18
 
19
19
  context 'internals', :api_private do
20
20
  it 'delegates to the default Logger object' do
21
- received = false
22
21
  expect(subject.logger).to be_a(::Logger)
23
- allow_any_instance_of(::Logger).to receive(:warn) do |*args, &block|
24
- expect(args.concat([block ? block.call : nil]).join(',')).to match(/message/)
25
- received = true
26
- end
22
+ expect(subject.logger).to receive(:warn).with('message')
23
+
27
24
  subject.warn 'message'
28
- expect(received).to be_truthy
29
25
  end
30
26
 
31
27
  context 'formatter' do
@@ -136,18 +132,14 @@ describe Ably::Logger do
136
132
  end
137
133
 
138
134
  it 'delegates log messages to logger', :api_private do
139
- received = false
140
- allow(custom_logger_object).to receive(:fatal) do |*args, &block|
141
- expect(args.concat([block ? block.call : nil]).join(',')).to match(/message/)
142
- received = true
143
- end
135
+ expect(custom_logger_object).to receive(:fatal).with('message')
136
+
144
137
  subject.fatal 'message'
145
- expect(received).to be_truthy
146
138
  end
147
139
  end
148
140
  end
149
141
 
150
- context 'with blocks', :prevent_log_stubbing do
142
+ context 'with blocks' do
151
143
  it 'does not call the block unless the log level is met' do
152
144
  log_level_blocks = []
153
145
  subject.warn { log_level_blocks << :warn }
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Models::DeltaExtras do
5
+ subject { described_class.new({ format: 'vcdiff', from: '1234-4567-8910-1001-1111'}) }
6
+
7
+ it 'should have `from` attribute' do
8
+ expect(subject.from).to eq('1234-4567-8910-1001-1111')
9
+ end
10
+
11
+ it 'should have `format` attribute' do
12
+ expect(subject.format).to eq('vcdiff')
13
+ end
14
+ end
@@ -5,7 +5,7 @@ describe Ably::Models::ErrorInfo do
5
5
  subject { Ably::Models::ErrorInfo }
6
6
 
7
7
  context '#TI1, #TI4' do
8
- it_behaves_like 'a model', with_simple_attributes: %w(code status_code href message) do
8
+ it_behaves_like 'a model', with_simple_attributes: %w(code status_code href message request_id cause) do
9
9
  let(:model_args) { [] }
10
10
  end
11
11
  end
@@ -18,6 +18,22 @@ describe Ably::Models::ErrorInfo do
18
18
  end
19
19
  end
20
20
 
21
+ context '#request_id #RSC7c' do
22
+ subject { Ably::Models::ErrorInfo.new('request_id' => '123-456-789-001') }
23
+
24
+ it 'should return request ID' do
25
+ expect(subject.request_id).to eql('123-456-789-001')
26
+ end
27
+ end
28
+
29
+ context '#cause #TI1' do
30
+ subject { Ably::Models::ErrorInfo.new('cause' => Ably::Models::ErrorInfo.new({})) }
31
+
32
+ it 'should return cause attribute' do
33
+ expect(subject.cause).to be_kind_of(Ably::Models::ErrorInfo)
34
+ end
35
+ end
36
+
21
37
  context 'log entries container help link #TI5' do
22
38
  context 'without an error code' do
23
39
  subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
@@ -270,6 +270,20 @@ describe Ably::Models::Message do
270
270
  end
271
271
  end
272
272
 
273
+ describe '#protocol_message_index (#RTL21)' do
274
+ let(:messages) { [{ name: 'test1' }, { name: 'test2' }, { name: 'test3' }] }
275
+
276
+ let(:protocol_message) do
277
+ Ably::Models::ProtocolMessage.new({ action: 1 }.merge(messages: messages))
278
+ end
279
+
280
+ it 'should return correct protocol_message_index' do
281
+ expect(protocol_message.messages[0].protocol_message_index).to eq(0)
282
+ expect(protocol_message.messages[1].protocol_message_index).to eq(1)
283
+ expect(protocol_message.messages[2].protocol_message_index).to eq(2)
284
+ end
285
+ end
286
+
273
287
  context 'from REST request with embedded fields', :api_private do
274
288
  let(:id) { random_str }
275
289
  let(:protocol_message_id) { random_str }
@@ -606,4 +620,28 @@ describe Ably::Models::Message do
606
620
  end
607
621
  end
608
622
  end
623
+
624
+ context '#delta_extras (TM2i)' do
625
+ let(:delta_extras) { message.delta_extras }
626
+
627
+ context 'when delta' do
628
+ let(:message) { subject.new({ extras: { delta: { from: '1234-1234-5678-9009', format: 'vcdiff' } } }) }
629
+
630
+ it 'should return vcdiff format' do
631
+ expect(delta_extras.format).to eq('vcdiff')
632
+ end
633
+
634
+ it 'should return 1234-1234-5678-9009 message id' do
635
+ expect(delta_extras.from).to eq('1234-1234-5678-9009')
636
+ end
637
+ end
638
+
639
+ context 'when no delta' do
640
+ let(:message) { subject.new({ extras: {} }) }
641
+
642
+ it 'should return nil' do
643
+ expect(delta_extras).to eq(nil)
644
+ end
645
+ end
646
+ end
609
647
  end
@@ -10,9 +10,9 @@ describe Ably::Models::ProtocolMessage do
10
10
  subject.new({ action: 1 }.merge(options))
11
11
  end
12
12
 
13
- # TR4n, TR4b, TR4c, TR4d, TR4e
13
+ # TR4n, TR4b, TR4c, TR4d
14
14
  it_behaves_like 'a model',
15
- with_simple_attributes: %w(id channel channel_serial connection_id connection_key),
15
+ with_simple_attributes: %w(id channel channel_serial connection_id),
16
16
  base_model_options: { action: 1 } do
17
17
 
18
18
  let(:model_args) { [] }
@@ -176,6 +176,28 @@ describe Ably::Models::ProtocolMessage do
176
176
  end
177
177
  end
178
178
 
179
+ context 'when attach resumed flag' do
180
+ context 'flags is 34' do
181
+ let(:protocol_message) { new_protocol_message(flags: 34) }
182
+
183
+ it '#has_attach_resume_flag? is true' do
184
+ expect(protocol_message.has_attach_resume_flag?).to be_truthy
185
+ end
186
+
187
+ it '#has_attach_presence_flag? is false' do
188
+ expect(protocol_message.has_attach_presence_flag?).to be_falsey
189
+ end
190
+ end
191
+
192
+ context 'flags is 0' do
193
+ let(:protocol_message) { new_protocol_message(flags: 0) }
194
+
195
+ it 'should raise an exception if flags is a float number' do
196
+ expect(protocol_message.has_attach_resume_flag?).to be_falsy
197
+ end
198
+ end
199
+ end
200
+
179
201
  context 'when channel resumed and presence flags present' do
180
202
  let(:protocol_message) { new_protocol_message(flags: 5) }
181
203
 
@@ -201,6 +223,24 @@ describe Ably::Models::ProtocolMessage do
201
223
  end
202
224
  end
203
225
 
226
+ context '#params (#RTL4k1)' do
227
+ let(:params) do
228
+ { foo: :bar }
229
+ end
230
+
231
+ context 'when present' do
232
+ specify do
233
+ expect(new_protocol_message({ params: params }).params).to eq(params)
234
+ end
235
+ end
236
+
237
+ context 'when empty' do
238
+ specify do
239
+ expect(new_protocol_message({}).params).to eq({})
240
+ end
241
+ end
242
+ end
243
+
204
244
  context '#has_connection_serial?' do
205
245
  context 'without connection_serial' do
206
246
  let(:protocol_message) { new_protocol_message({}) }
@@ -308,6 +348,26 @@ describe Ably::Models::ProtocolMessage do
308
348
  end
309
349
  end
310
350
 
351
+ context '#messages (#RTL21)' do
352
+ let(:protocol_message) do
353
+ new_protocol_message(messages: [{ name: 'test1' }, { name: 'test2' }, { name: 'test3' }])
354
+ end
355
+
356
+ before do
357
+ message = Ably::Models::Message(name: 'test4')
358
+ message.assign_to_protocol_message(protocol_message)
359
+ protocol_message.add_message(message)
360
+ end
361
+
362
+ it 'contains Message objects in ascending order' do
363
+ expect(protocol_message.messages.count).to eql(4)
364
+ protocol_message.messages.each_with_index do |message, index|
365
+ expect(message.protocol_message_index).to eql(index)
366
+ expect(message.name).to include('test')
367
+ end
368
+ end
369
+ end
370
+
311
371
  context '#presence (#TR4l)' do
312
372
  let(:protocol_message) { new_protocol_message(presence: [{ action: 1, data: 'test' }]) }
313
373
 
@@ -417,41 +477,27 @@ describe Ably::Models::ProtocolMessage do
417
477
  end
418
478
  end
419
479
  end
420
-
421
- context '#connection_key (#TR4e)' do
422
- context 'existing only in #connection_details.connection_key' do
423
- let(:protocol_message) { new_protocol_message(connectionDetails: { connectionKey: 'key' }) }
424
-
425
- it 'is returned' do
426
- expect(protocol_message.connection_key).to eql('key')
427
- end
428
- end
429
-
430
- context 'existing in both #connection_key and #connection_details.connection_key' do
431
- let(:protocol_message) { new_protocol_message(connectionKey: 'deprecated', connectionDetails: { connectionKey: 'key' }) }
432
-
433
- it 'returns #connection_details.connection_key as #connection_key will be deprecated > 0.8' do
434
- expect(protocol_message.connection_key).to eql('key')
435
- end
436
- end
437
- end
438
480
  end
439
481
 
440
482
  context '#to_json', :api_private do
441
483
  let(:json_object) { JSON.parse(model.to_json) }
442
- let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
484
+ let(:message1) { { 'name' => 'event1', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
485
+ let(:message2) { { 'name' => 'event2', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
486
+ let(:message3) { { 'name' => 'event3', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
443
487
  let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached }
444
488
  let(:message_action) { Ably::Models::ProtocolMessage::ACTION.Message }
445
489
 
446
490
  context 'with valid data' do
447
- let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message] }) }
491
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message1, message2, message3] }) }
448
492
 
449
493
  it 'converts the attribute back to Java mixedCase notation using string keys' do
450
494
  expect(json_object["channelSerial"]).to eql('unique')
451
495
  end
452
496
 
453
497
  it 'populates the messages' do
454
- expect(json_object["messages"].first).to include(message)
498
+ expect(json_object["messages"][0]).to include(message1)
499
+ expect(json_object["messages"][1]).to include(message2)
500
+ expect(json_object["messages"][2]).to include(message3)
455
501
  end
456
502
  end
457
503
 
@@ -464,7 +510,7 @@ describe Ably::Models::ProtocolMessage do
464
510
  end
465
511
 
466
512
  context 'is aliased by #to_s' do
467
- let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message], :timestamp => as_since_epoch(Time.now) }) }
513
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message1, message2, message3], :timestamp => as_since_epoch(Time.now) }) }
468
514
 
469
515
  specify do
470
516
  expect(json_object).to eql(JSON.parse("#{model}"))
@@ -473,14 +519,18 @@ describe Ably::Models::ProtocolMessage do
473
519
  end
474
520
 
475
521
  context '#to_msgpack', :api_private do
476
- let(:model) { new_protocol_message({ :connectionSerial => 'unique', messages: [message] }) }
477
- let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
522
+ let(:model) { new_protocol_message({ :connectionSerial => 'unique', messages: [message1, message2, message3] }) }
523
+ let(:message1) { { 'name' => 'event1', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
524
+ let(:message2) { { 'name' => 'event2', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
525
+ let(:message3) { { 'name' => 'event3', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
478
526
  let(:packed) { model.to_msgpack }
479
527
  let(:unpacked) { MessagePack.unpack(packed) }
480
528
 
481
529
  it 'returns a unpackable msgpack object' do
482
530
  expect(unpacked['connectionSerial']).to eq('unique')
483
- expect(unpacked['messages'][0]['name']).to eq('event')
531
+ expect(unpacked['messages'][0]['name']).to eq('event1')
532
+ expect(unpacked['messages'][1]['name']).to eq('event2')
533
+ expect(unpacked['messages'][2]['name']).to eq('event3')
484
534
  end
485
535
  end
486
536
  end
@@ -80,6 +80,20 @@ describe Ably::Models::TokenDetails do
80
80
  expect(subject.expired?).to eql(false)
81
81
  end
82
82
  end
83
+
84
+ context 'with :from attribute' do
85
+ subject { Ably::Models::TokenDetails.new(expires: expire_time) }
86
+
87
+ let(:server_offset_time) { 2 * 60 * 60 } # 2 hours
88
+
89
+ it 'is false' do
90
+ expect(subject.expired?(from: (Time.now - server_offset_time))).to eql(false)
91
+ end
92
+
93
+ it 'is true' do
94
+ expect(subject.expired?(from: Time.now)).to eql(true)
95
+ end
96
+ end
83
97
  end
84
98
  end
85
99
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  require 'shared/protocol_msgbus_behaviour'
4
4
 
5
5
  describe Ably::Realtime::Channel do
6
- let(:client) { double('client').as_null_object }
6
+ let(:client) { Ably::Realtime::Client.new(token: 'valid') }
7
7
  let(:channel_name) { 'test' }
8
8
 
9
9
  subject do
@@ -71,6 +71,7 @@ describe Ably::Realtime::Channel do
71
71
  let(:message) { instance_double('Ably::Models::Message', client_id: nil, size: 0) }
72
72
 
73
73
  before do
74
+ allow(subject).to receive(:enqueue_messages_on_connection).and_return(message)
74
75
  allow(subject).to receive(:create_message).and_return(message)
75
76
  allow(subject).to receive(:attach).and_return(:true)
76
77
  end
@@ -3,39 +3,77 @@ require 'spec_helper'
3
3
 
4
4
  describe Ably::Realtime::Channels do
5
5
  let(:connection) { instance_double('Ably::Realtime::Connection', unsafe_on: true, on_resume: true) }
6
- let(:client) { instance_double('Ably::Realtime::Client', connection: connection, client_id: 'clientId') }
6
+ let(:client) do
7
+ instance_double('Ably::Realtime::Client', connection: connection, client_id: 'clientId', logger: double('logger').as_null_object)
8
+ end
7
9
  let(:channel_name) { 'unique' }
8
- let(:options) { { 'bizarre' => 'value' } }
10
+ let(:options) do
11
+ { params: { bizarre: 'value' } }
12
+ end
9
13
 
10
14
  subject { Ably::Realtime::Channels.new(client) }
11
15
 
12
16
  context 'creating channels' do
13
17
  context '#get' do
14
- it 'creates a channel if it does not exist' do
15
- expect(Ably::Realtime::Channel).to receive(:new).with(client, channel_name, options)
16
- subject.get(channel_name, options)
18
+ context "when channel doesn't exist" do
19
+ shared_examples 'creates a channel' do
20
+ it 'creates a channel (RTS3a)' do
21
+ expect(Ably::Realtime::Channel).to receive(:new).with(client, channel_name, channel_options)
22
+ subject.get(channel_name, channel_options)
23
+ end
24
+ end
25
+
26
+ describe 'hash' do
27
+ let(:channel_options) { options }
28
+ it { expect(channel_options).to be_a(Hash) }
29
+
30
+ include_examples 'creates a channel'
31
+ end
32
+
33
+ describe 'ChannelOptions object' do
34
+ let(:channel_options) { Ably::Models::ChannelOptions.new(options) }
35
+ it { expect(channel_options).to be_a(Ably::Models::ChannelOptions) }
36
+
37
+ include_examples 'creates a channel'
38
+ end
17
39
  end
18
40
 
19
41
  context 'when an existing channel exists' do
20
- it 'will reuse a channel object if it exists' do
21
- channel = subject.get(channel_name, options)
22
- expect(channel).to be_a(Ably::Realtime::Channel)
23
- expect(subject.get(channel_name, options).object_id).to eql(channel.object_id)
42
+ shared_examples 'reuse a channel object if it exists' do
43
+ it 'will reuse a channel object if it exists (RTS3a)' do
44
+ channel = subject.get(channel_name, channel_options)
45
+ expect(channel).to be_a(Ably::Realtime::Channel)
46
+ expect(subject.get(channel_name, channel_options).object_id).to eql(channel.object_id)
47
+ end
48
+ end
49
+
50
+ describe 'hash' do
51
+ let(:channel_options) { options }
52
+ it { expect(channel_options).to be_a(Hash) }
53
+
54
+ include_examples 'reuse a channel object if it exists'
55
+ end
56
+
57
+ describe 'ChannelOptions object' do
58
+ let(:channel_options) { Ably::Models::ChannelOptions.new(options) }
59
+ it { expect(channel_options).to be_a(Ably::Models::ChannelOptions) }
60
+
61
+ include_examples 'reuse a channel object if it exists'
24
62
  end
25
63
 
26
- it 'will update the options on the channel if provided' do
64
+ it 'will update the options on the channel if provided (RSN3c)' do
27
65
  channel = subject.get(channel_name, options)
28
- expect(channel.options).to eql(options)
29
- expect(channel.options).to_not include(:encrypted)
66
+ expect(channel.options.to_h).to eq(options)
67
+ expect(channel.options.to_h).to_not include(:encrypted)
30
68
  subject.get(channel_name, encrypted: true)
31
- expect(channel.options[:encrypted]).to eql(true)
69
+ expect(channel.options[:encrypted]).to eq(true)
32
70
  end
33
71
 
34
72
  it 'will leave the options intact on the channel if not provided' do
35
73
  channel = subject.get(channel_name, options)
36
- expect(channel.options).to eql(options)
74
+ expect(channel.options.to_h).to eq(options)
37
75
  subject.get(channel_name)
38
- expect(channel.options).to eql(options)
76
+ expect(channel.options.to_h).to eq(options)
39
77
  end
40
78
  end
41
79
  end