ably 1.1.8 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +14 -5
  3. data/CHANGELOG.md +48 -0
  4. data/README.md +2 -2
  5. data/UPDATING.md +30 -0
  6. data/ably.gemspec +12 -24
  7. data/lib/ably/auth.rb +8 -8
  8. data/lib/ably/logger.rb +4 -4
  9. data/lib/ably/models/channel_details.rb +59 -0
  10. data/lib/ably/models/channel_metrics.rb +84 -0
  11. data/lib/ably/models/channel_occupancy.rb +43 -0
  12. data/lib/ably/models/channel_options.rb +97 -0
  13. data/lib/ably/models/channel_status.rb +53 -0
  14. data/lib/ably/models/device_details.rb +1 -1
  15. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -0
  16. data/lib/ably/models/message.rb +4 -4
  17. data/lib/ably/models/protocol_message.rb +19 -7
  18. data/lib/ably/models/token_details.rb +7 -2
  19. data/lib/ably/models/token_request.rb +1 -1
  20. data/lib/ably/modules/ably.rb +1 -1
  21. data/lib/ably/modules/channels_collection.rb +22 -2
  22. data/lib/ably/modules/conversions.rb +34 -0
  23. data/lib/ably/realtime/auth.rb +2 -2
  24. data/lib/ably/realtime/channel/channel_manager.rb +16 -4
  25. data/lib/ably/realtime/channel/channel_state_machine.rb +5 -0
  26. data/lib/ably/realtime/channel.rb +54 -24
  27. data/lib/ably/realtime/channels.rb +1 -1
  28. data/lib/ably/rest/channel.rb +33 -34
  29. data/lib/ably/rest/client.rb +8 -5
  30. data/lib/ably/rest/middleware/encoder.rb +1 -1
  31. data/lib/ably/rest/middleware/exceptions.rb +1 -1
  32. data/lib/ably/rest/middleware/external_exceptions.rb +1 -1
  33. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +1 -1
  34. data/lib/ably/rest/middleware/logger.rb +1 -1
  35. data/lib/ably/rest/middleware/parse_json.rb +1 -1
  36. data/lib/ably/rest/middleware/parse_message_pack.rb +1 -1
  37. data/lib/ably/util/crypto.rb +1 -1
  38. data/lib/ably/version.rb +2 -2
  39. data/spec/acceptance/realtime/channel_spec.rb +247 -21
  40. data/spec/acceptance/realtime/channels_spec.rb +59 -7
  41. data/spec/acceptance/realtime/connection_spec.rb +21 -1
  42. data/spec/acceptance/realtime/message_spec.rb +77 -0
  43. data/spec/acceptance/rest/auth_spec.rb +18 -0
  44. data/spec/acceptance/rest/channel_spec.rb +19 -1
  45. data/spec/acceptance/rest/channels_spec.rb +22 -5
  46. data/spec/acceptance/rest/client_spec.rb +3 -3
  47. data/spec/acceptance/rest/message_spec.rb +61 -3
  48. data/spec/lib/unit/models/channel_options_spec.rb +52 -0
  49. data/spec/run_parallel_tests +2 -7
  50. data/spec/unit/logger_spec.rb +6 -14
  51. data/spec/unit/models/channel_details_spec.rb +30 -0
  52. data/spec/unit/models/channel_metrics_spec.rb +42 -0
  53. data/spec/unit/models/channel_occupancy_spec.rb +17 -0
  54. data/spec/unit/models/channel_status_spec.rb +36 -0
  55. data/spec/unit/models/message_spec.rb +14 -0
  56. data/spec/unit/models/protocol_message_spec.rb +53 -7
  57. data/spec/unit/models/token_details_spec.rb +14 -0
  58. data/spec/unit/realtime/channels_spec.rb +52 -14
  59. data/spec/unit/rest/channels_spec.rb +81 -14
  60. metadata +69 -11
@@ -5,11 +5,11 @@ describe Ably::Rest::Channels do
5
5
  shared_examples 'a channel' do
6
6
  it 'returns a channel object' do
7
7
  expect(channel).to be_a Ably::Rest::Channel
8
- expect(channel.name).to eql(channel_name)
8
+ expect(channel.name).to eq(channel_name)
9
9
  end
10
10
 
11
11
  it 'returns channel object and passes the provided options' do
12
- expect(channel_with_options.options).to eql(options)
12
+ expect(channel_with_options.options.to_h).to eq(options)
13
13
  end
14
14
  end
15
15
 
@@ -32,12 +32,29 @@ describe Ably::Rest::Channels do
32
32
  it_behaves_like 'a channel'
33
33
  end
34
34
 
35
+ describe '#set_options (#RTL16)' do
36
+ let(:channel) { client.channel(channel_name) }
37
+
38
+ it "updates channel's options" do
39
+ expect { channel.options = options }.to change { channel.options.to_h }.from({}).to(options)
40
+ end
41
+
42
+ context 'when providing Ably::Models::ChannelOptions object' do
43
+ let(:options_object) { Ably::Models::ChannelOptions.new(options) }
44
+
45
+ it "updates channel's options" do
46
+ expect { channel.options = options_object}.to change { channel.options.to_h }.from({}).to(options)
47
+ end
48
+ end
49
+ end
50
+
35
51
  describe 'accessing an existing channel object with different options' do
36
52
  let(:new_channel_options) { { encrypted: true } }
37
53
  let(:original_channel) { client.channels.get(channel_name, options) }
38
54
 
39
55
  it 'overrides the existing channel options and returns the channel object (RSN3c)' do
40
- expect(original_channel.options).to_not include(:encrypted)
56
+ expect(original_channel.options.to_h).to_not include(:encrypted)
57
+
41
58
  new_channel = client.channels.get(channel_name, new_channel_options)
42
59
  expect(new_channel).to be_a(Ably::Rest::Channel)
43
60
  expect(new_channel.options[:encrypted]).to eql(true)
@@ -48,10 +65,10 @@ describe Ably::Rest::Channels do
48
65
  let(:original_channel) { client.channels.get(channel_name, options) }
49
66
 
50
67
  it 'returns the existing channel without modifying the channel options' do
51
- expect(original_channel.options).to eql(options)
68
+ expect(original_channel.options.to_h).to eq(options)
52
69
  new_channel = client.channels.get(channel_name)
53
70
  expect(new_channel).to be_a(Ably::Rest::Channel)
54
- expect(original_channel.options).to eql(options)
71
+ expect(original_channel.options.to_h).to eq(options)
55
72
  end
56
73
  end
57
74
 
@@ -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
@@ -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,30 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::ChannelDetails do
5
+ subject { Ably::Models::ChannelDetails(channel_id: 'channel-id-123-xyz', name: 'name', status: { isActive: 'true', occupancy: { metrics: { connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9 } } }) }
6
+
7
+ describe '#channel_id' do
8
+ it 'should return channel id' do
9
+ expect(subject.channel_id).to eq('channel-id-123-xyz')
10
+ end
11
+ end
12
+
13
+ describe '#name' do
14
+ it 'should return name' do
15
+ expect(subject.name).to eq('name')
16
+ end
17
+ end
18
+
19
+ describe '#status' do
20
+ it 'should return status' do
21
+ expect(subject.status).to be_a(Ably::Models::ChannelStatus)
22
+ expect(subject.status.occupancy.metrics.connections).to eq(1)
23
+ expect(subject.status.occupancy.metrics.presence_connections).to eq(2)
24
+ expect(subject.status.occupancy.metrics.presence_members).to eq(2)
25
+ expect(subject.status.occupancy.metrics.presence_subscribers).to eq(5)
26
+ expect(subject.status.occupancy.metrics.publishers).to eq(7)
27
+ expect(subject.status.occupancy.metrics.subscribers).to eq(9)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::ChannelMetrics do
5
+ subject { Ably::Models::ChannelMetrics(connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9) }
6
+
7
+ describe '#connections' do
8
+ it 'should return integer' do
9
+ expect(subject.connections).to eq(1)
10
+ end
11
+ end
12
+
13
+ describe '#presence_connections' do
14
+ it 'should return integer' do
15
+ expect(subject.presence_connections).to eq(2)
16
+ end
17
+ end
18
+
19
+ describe '#presence_members' do
20
+ it 'should return integer' do
21
+ expect(subject.presence_members).to eq(2)
22
+ end
23
+ end
24
+
25
+ describe '#presence_subscribers' do
26
+ it 'should return integer' do
27
+ expect(subject.presence_subscribers).to eq(5)
28
+ end
29
+ end
30
+
31
+ describe '#publishers' do
32
+ it 'should return integer' do
33
+ expect(subject.publishers).to eq(7)
34
+ end
35
+ end
36
+
37
+ describe '#subscribers' do
38
+ it 'should return integer' do
39
+ expect(subject.subscribers).to eq(9)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::ChannelOccupancy do
5
+ subject { Ably::Models::ChannelOccupancy({ metrics: { connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9 } }) }
6
+
7
+ describe '#metrics' do
8
+ it 'should return attributes' do
9
+ expect(subject.metrics.connections).to eq(1)
10
+ expect(subject.metrics.presence_connections).to eq(2)
11
+ expect(subject.metrics.presence_members).to eq(2)
12
+ expect(subject.metrics.presence_subscribers).to eq(5)
13
+ expect(subject.metrics.publishers).to eq(7)
14
+ expect(subject.metrics.subscribers).to eq(9)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::ChannelStatus do
5
+ subject { Ably::Models::ChannelStatus({ isActive: 'true', occupancy: { metrics: { connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9 } } }) }
6
+
7
+ describe '#is_active' do
8
+ context 'when occupancy is active' do
9
+ subject { Ably::Models::ChannelStatus({ isActive: true, occupancy: { metrics: { connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9 } } }) }
10
+
11
+ it 'should return true' do
12
+ expect(subject.is_active).to eq(true)
13
+ end
14
+ end
15
+
16
+ context 'when occupancy is not active' do
17
+ subject { Ably::Models::ChannelStatus({ isActive: false, occupancy: { metrics: { connections: 1, presence_connections: 2, presence_members: 2, presence_subscribers: 5, publishers: 7, subscribers: 9 } } }) }
18
+
19
+ it 'should return false' do
20
+ expect(subject.is_active).to eq(false)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#occupancy' do
26
+ it 'should return occupancy object' do
27
+ expect(subject.occupancy).to be_a(Ably::Models::ChannelOccupancy)
28
+ expect(subject.occupancy.metrics.connections).to eq(1)
29
+ expect(subject.occupancy.metrics.presence_connections).to eq(2)
30
+ expect(subject.occupancy.metrics.presence_members).to eq(2)
31
+ expect(subject.occupancy.metrics.presence_subscribers).to eq(5)
32
+ expect(subject.occupancy.metrics.publishers).to eq(7)
33
+ expect(subject.occupancy.metrics.subscribers).to eq(9)
34
+ end
35
+ end
36
+ end
@@ -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 }
@@ -223,6 +223,24 @@ describe Ably::Models::ProtocolMessage do
223
223
  end
224
224
  end
225
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
+
226
244
  context '#has_connection_serial?' do
227
245
  context 'without connection_serial' do
228
246
  let(:protocol_message) { new_protocol_message({}) }
@@ -330,6 +348,26 @@ describe Ably::Models::ProtocolMessage do
330
348
  end
331
349
  end
332
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
+
333
371
  context '#presence (#TR4l)' do
334
372
  let(:protocol_message) { new_protocol_message(presence: [{ action: 1, data: 'test' }]) }
335
373
 
@@ -443,19 +481,23 @@ describe Ably::Models::ProtocolMessage do
443
481
 
444
482
  context '#to_json', :api_private do
445
483
  let(:json_object) { JSON.parse(model.to_json) }
446
- 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) } }
447
487
  let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached }
448
488
  let(:message_action) { Ably::Models::ProtocolMessage::ACTION.Message }
449
489
 
450
490
  context 'with valid data' do
451
- 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] }) }
452
492
 
453
493
  it 'converts the attribute back to Java mixedCase notation using string keys' do
454
494
  expect(json_object["channelSerial"]).to eql('unique')
455
495
  end
456
496
 
457
497
  it 'populates the messages' do
458
- 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)
459
501
  end
460
502
  end
461
503
 
@@ -468,7 +510,7 @@ describe Ably::Models::ProtocolMessage do
468
510
  end
469
511
 
470
512
  context 'is aliased by #to_s' do
471
- 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) }) }
472
514
 
473
515
  specify do
474
516
  expect(json_object).to eql(JSON.parse("#{model}"))
@@ -477,14 +519,18 @@ describe Ably::Models::ProtocolMessage do
477
519
  end
478
520
 
479
521
  context '#to_msgpack', :api_private do
480
- let(:model) { new_protocol_message({ :connectionSerial => 'unique', messages: [message] }) }
481
- 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) } }
482
526
  let(:packed) { model.to_msgpack }
483
527
  let(:unpacked) { MessagePack.unpack(packed) }
484
528
 
485
529
  it 'returns a unpackable msgpack object' do
486
530
  expect(unpacked['connectionSerial']).to eq('unique')
487
- 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')
488
534
  end
489
535
  end
490
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,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 (RSN3a)' 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 (RSN3a)' 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
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