ably 1.1.6 → 1.2.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +15 -1
  3. data/CHANGELOG.md +131 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +14 -2
  6. data/SPEC.md +0 -7
  7. data/UPDATING.md +30 -0
  8. data/ably.gemspec +12 -7
  9. data/lib/ably/agent.rb +3 -0
  10. data/lib/ably/auth.rb +3 -3
  11. data/lib/ably/exceptions.rb +6 -0
  12. data/lib/ably/models/channel_options.rb +97 -0
  13. data/lib/ably/models/connection_details.rb +8 -0
  14. data/lib/ably/models/delta_extras.rb +29 -0
  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 +28 -3
  18. data/lib/ably/models/presence_message.rb +14 -0
  19. data/lib/ably/models/protocol_message.rb +29 -12
  20. data/lib/ably/models/token_details.rb +7 -2
  21. data/lib/ably/modules/channels_collection.rb +22 -2
  22. data/lib/ably/modules/conversions.rb +34 -0
  23. data/lib/ably/realtime/channel/channel_manager.rb +18 -6
  24. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
  25. data/lib/ably/realtime/channel/publisher.rb +6 -0
  26. data/lib/ably/realtime/channel.rb +54 -22
  27. data/lib/ably/realtime/channels.rb +1 -1
  28. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  29. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  31. data/lib/ably/realtime/connection.rb +2 -2
  32. data/lib/ably/rest/channel.rb +31 -31
  33. data/lib/ably/rest/client.rb +27 -12
  34. data/lib/ably/util/crypto.rb +1 -1
  35. data/lib/ably/version.rb +2 -14
  36. data/lib/ably.rb +1 -0
  37. data/spec/acceptance/realtime/auth_spec.rb +1 -1
  38. data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  39. data/spec/acceptance/realtime/channel_spec.rb +466 -21
  40. data/spec/acceptance/realtime/channels_spec.rb +59 -7
  41. data/spec/acceptance/realtime/connection_failures_spec.rb +59 -2
  42. data/spec/acceptance/realtime/connection_spec.rb +256 -28
  43. data/spec/acceptance/realtime/message_spec.rb +77 -0
  44. data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
  45. data/spec/acceptance/realtime/presence_spec.rb +31 -159
  46. data/spec/acceptance/rest/auth_spec.rb +18 -0
  47. data/spec/acceptance/rest/channel_spec.rb +84 -9
  48. data/spec/acceptance/rest/channels_spec.rb +23 -6
  49. data/spec/acceptance/rest/client_spec.rb +25 -21
  50. data/spec/acceptance/rest/message_spec.rb +61 -3
  51. data/spec/lib/unit/models/channel_options_spec.rb +52 -0
  52. data/spec/shared/model_behaviour.rb +1 -1
  53. data/spec/spec_helper.rb +11 -2
  54. data/spec/support/test_app.rb +1 -1
  55. data/spec/unit/models/delta_extras_spec.rb +14 -0
  56. data/spec/unit/models/error_info_spec.rb +17 -1
  57. data/spec/unit/models/message_spec.rb +97 -0
  58. data/spec/unit/models/presence_message_spec.rb +49 -0
  59. data/spec/unit/models/protocol_message_spec.rb +125 -27
  60. data/spec/unit/models/token_details_spec.rb +14 -0
  61. data/spec/unit/realtime/channel_spec.rb +3 -2
  62. data/spec/unit/realtime/channels_spec.rb +53 -15
  63. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  64. data/spec/unit/rest/channel_spec.rb +44 -1
  65. data/spec/unit/rest/channels_spec.rb +81 -14
  66. data/spec/unit/rest/client_spec.rb +47 -0
  67. metadata +60 -24
@@ -498,7 +498,9 @@ describe Ably::Realtime::Presence, :event_machine do
498
498
  end
499
499
 
500
500
  presence_client_one.enter do
501
- presence_client_one.leave
501
+ EventMachine.add_timer(0.5) do
502
+ presence_client_one.leave
503
+ end
502
504
  end
503
505
  end
504
506
  end
@@ -705,7 +707,7 @@ describe Ably::Realtime::Presence, :event_machine do
705
707
 
706
708
  context '#sync_complete? and SYNC flags (#RTP1)' do
707
709
  context 'when attaching to a channel without any members present' do
708
- it 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do
710
+ xit 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do
709
711
  flag_checked = false
710
712
 
711
713
  anonymous_client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
@@ -1211,10 +1213,11 @@ describe Ably::Realtime::Presence, :event_machine do
1211
1213
  channel_client_one.attach do
1212
1214
  presence_client_one.subscribe(:enter) do
1213
1215
  presence_client_one.unsubscribe :enter
1214
-
1215
- expect(presence_client_one.members).to be_in_sync
1216
- expect(presence_client_one.members.send(:members).count).to eql(1)
1217
- presence_client_one.leave data
1216
+ EventMachine.add_timer(0.5) do
1217
+ expect(presence_client_one.members).to be_in_sync
1218
+ expect(presence_client_one.members.send(:members).count).to eql(1)
1219
+ presence_client_one.leave data
1220
+ end
1218
1221
  end
1219
1222
 
1220
1223
  presence_client_one.enter(enter_data) do
@@ -2137,16 +2140,18 @@ describe Ably::Realtime::Presence, :event_machine do
2137
2140
  end
2138
2141
 
2139
2142
  it 'emits an error when cipher does not match and presence data cannot be decoded' do
2140
- incompatible_encrypted_channel.attach do
2143
+ incompatible_encrypted_channel.once(:attached) do
2141
2144
  expect(client_two.logger).to receive(:error) do |*args, &block|
2142
2145
  expect(args.concat([block ? block.call : nil]).join(',')).to match(/Cipher algorithm AES-128-CBC does not match/)
2143
2146
  stop_reactor
2144
- end
2147
+ end.at_least(:once)
2145
2148
 
2146
2149
  encrypted_channel.attach do
2147
2150
  encrypted_channel.presence.enter data
2148
2151
  end
2149
2152
  end
2153
+
2154
+ incompatible_encrypted_channel.attach
2150
2155
  end
2151
2156
  end
2152
2157
  end
@@ -2522,156 +2527,6 @@ describe Ably::Realtime::Presence, :event_machine do
2522
2527
  client_one.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
2523
2528
  end
2524
2529
 
2525
- context 'and the resume flag is true' do
2526
- context 'and the presence flag is false' do
2527
- it 'does not send any presence events as the PresenceMap is in sync (#RTP5c1)' do
2528
- presence_client_one.enter
2529
- presence_client_one.subscribe(:enter) do
2530
- presence_client_one.unsubscribe :enter
2531
-
2532
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2533
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2534
- end
2535
-
2536
- cripple_websocket_transport
2537
-
2538
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2539
- action: attached_action,
2540
- channel: channel_name,
2541
- flags: resume_flag
2542
- )
2543
-
2544
- EventMachine.add_timer(1) do
2545
- presence_client_one.get do |members|
2546
- expect(members.length).to eql(1)
2547
- expect(presence_client_one.members.local_members.length).to eql(1)
2548
- stop_reactor
2549
- end
2550
- end
2551
- end
2552
- end
2553
- end
2554
-
2555
- context 'and the presence flag is true' do
2556
- context 'and following the SYNC all local MemberMap members are present in the PresenceMap' do
2557
- it 'does nothing as MemberMap is in sync (#RTP5c2)' do
2558
- presence_client_one.enter
2559
- presence_client_one.subscribe(:enter) do
2560
- presence_client_one.unsubscribe :enter
2561
-
2562
- expect(presence_client_one.members.length).to eql(1)
2563
- expect(presence_client_one.members.local_members.length).to eql(1)
2564
-
2565
- presence_client_one.members.once(:in_sync) do
2566
- presence_client_one.get do |members|
2567
- expect(members.length).to eql(1)
2568
- expect(presence_client_one.members.local_members.length).to eql(1)
2569
- stop_reactor
2570
- end
2571
- end
2572
-
2573
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2574
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2575
- end
2576
-
2577
- cripple_websocket_transport
2578
-
2579
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2580
- action: attached_action,
2581
- channel: channel_name,
2582
- flags: resume_flag + presence_flag
2583
- )
2584
-
2585
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2586
- action: sync_action,
2587
- channel: channel_name,
2588
- presence: presence_client_one.members.map(&:shallow_clone).map(&:as_json),
2589
- channelSerial: nil # no further SYNC messages expected
2590
- )
2591
- end
2592
- end
2593
- end
2594
-
2595
- context 'and following the SYNC a local MemberMap member is not present in the PresenceMap' do
2596
- it 're-enters the missing members automatically (#RTP5c2)' do
2597
- sync_check_completed = false
2598
-
2599
- presence_client_one.enter
2600
- presence_client_one.subscribe(:enter) do
2601
- presence_client_one.unsubscribe :enter
2602
-
2603
- expect(presence_client_one.members.length).to eql(1)
2604
- expect(presence_client_one.members.local_members.length).to eql(1)
2605
-
2606
- client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |message|
2607
- next if message.action == :close # ignore finalization of connection
2608
-
2609
- expect(message.action).to eq(:presence)
2610
- presence_message = message.presence.first
2611
- expect(presence_message.action).to eq(:enter)
2612
- expect(presence_message.client_id).to eq(client_one.auth.client_id)
2613
-
2614
- presence_client_one.subscribe(:enter) do |message|
2615
- expect(message.connection_id).to eql(client_one.connection.id)
2616
- expect(message.client_id).to eq(client_one.auth.client_id)
2617
-
2618
- EventMachine.next_tick do
2619
- expect(presence_client_one.members.length).to eql(2)
2620
- expect(presence_client_one.members.local_members.length).to eql(1)
2621
- expect(sync_check_completed).to be_truthy
2622
- stop_reactor
2623
- end
2624
- end
2625
-
2626
- # Fabricate Ably sending back the Enter PresenceMessage to the client a short while after
2627
- # ensuring the PresenceMap for a short period does not have this member as to be expected in reality
2628
- EventMachine.add_timer(0.2) do
2629
- connection_id = random_str
2630
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2631
- action: presence_action,
2632
- channel: channel_name,
2633
- connectionId: client_one.connection.id,
2634
- connectionSerial: 50,
2635
- timestamp: as_since_epoch(Time.now),
2636
- presence: [presence_message.shallow_clone(id: "#{client_one.connection.id}:0:0", timestamp: as_since_epoch(Time.now)).as_json]
2637
- )
2638
- end
2639
- end
2640
-
2641
- presence_client_one.members.once(:in_sync) do
2642
- # For a brief period, the client will have re-entered the missing members from the local_members
2643
- # but the enter from Ably will have not been received, so at this point the local_members will be empty
2644
- presence_client_one.get do |members|
2645
- expect(members.length).to eql(1)
2646
- expect(members.first.connection_id).to_not eql(client_one.connection.id)
2647
- expect(presence_client_one.members.local_members.length).to eql(0)
2648
- sync_check_completed = true
2649
- end
2650
- end
2651
-
2652
- cripple_websocket_transport
2653
-
2654
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2655
- action: attached_action,
2656
- channel: channel_name,
2657
- flags: resume_flag + presence_flag
2658
- )
2659
-
2660
- # Complete the SYNC but without the member who was entered by this client
2661
- connection_id = random_str
2662
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2663
- action: sync_action,
2664
- channel: channel_name,
2665
- timestamp: as_since_epoch(Time.now),
2666
- presence: [{ id: "#{connection_id}:0:0", action: present_action, connection_id: connection_id, client_id: random_str }],
2667
- chanenlSerial: nil # no further SYNC messages expected
2668
- )
2669
- end
2670
- end
2671
- end
2672
- end
2673
- end
2674
-
2675
2530
  context 'and the resume flag is false' do
2676
2531
  context 'and the presence flag is false' do
2677
2532
  let(:member_data) { random_str }
@@ -2779,7 +2634,24 @@ describe Ably::Realtime::Presence, :event_machine do
2779
2634
  end
2780
2635
  end
2781
2636
 
2782
- context 'channel state side effects' do
2637
+ context 'channel state side effects (RTP5)' do
2638
+ context 'channel transitions to the ATTACHED state (RTP5b)' do
2639
+ it 'all queued presence messages are sent' do
2640
+ channel_client_one.on(:attached) do
2641
+ client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
2642
+ if protocol_message.action == :presence
2643
+ expect(protocol_message.action).to eq(:presence)
2644
+ stop_reactor
2645
+ end
2646
+ end
2647
+ end
2648
+
2649
+ presence_client_one.enter do
2650
+ channel_client_one.attach
2651
+ end
2652
+ end
2653
+ end
2654
+
2783
2655
  context 'channel transitions to the FAILED state' do
2784
2656
  let(:anonymous_client) { auto_close Ably::Realtime::Client.new(client_options.merge(log_level: :fatal)) }
2785
2657
  let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: client_one_id, log_level: :fatal)) }
@@ -1165,6 +1165,24 @@ describe Ably::Auth do
1165
1165
  end
1166
1166
  end
1167
1167
 
1168
+ context 'when token does not expire' do
1169
+ let(:client_options) { default_options.merge(use_token_auth: true, key: api_key, query_time: true) }
1170
+ let(:channel) { client.channels.get(random_str) }
1171
+
1172
+ context 'for the next 2 hours' do
1173
+ let(:local_time) { Time.now - 2 * 60 * 60 }
1174
+
1175
+ before { allow(Time).to receive(:now).and_return(local_time) }
1176
+
1177
+ it 'should not request for the new token (#RSA4b1)' do
1178
+ expect { channel.publish 'event' }.to change { auth.current_token_details }
1179
+ expect do
1180
+ expect { channel.publish 'event' }.not_to change { auth.current_token_details }
1181
+ end.not_to change { auth.current_token_details.expires }
1182
+ end
1183
+ end
1184
+ end
1185
+
1168
1186
  context 'when :client_id is provided in a token' do
1169
1187
  let(:client_id) { '123' }
1170
1188
  let(:token) do
@@ -5,11 +5,13 @@ describe Ably::Rest::Channel do
5
5
  include Ably::Modules::Conversions
6
6
 
7
7
  vary_by_protocol do
8
- let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
8
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, max_frame_size: max_frame_size, max_message_size: max_message_size, idempotent_rest_publishing: false } }
9
9
  let(:client_options) { default_options }
10
10
  let(:client) do
11
11
  Ably::Rest::Client.new(client_options)
12
12
  end
13
+ let(:max_message_size) { nil }
14
+ let(:max_frame_size) { nil }
13
15
 
14
16
  describe '#publish' do
15
17
  let(:channel_name) { random_str }
@@ -60,6 +62,9 @@ describe Ably::Rest::Channel do
60
62
  end
61
63
 
62
64
  it 'publishes an array of messages in one HTTP request' do
65
+ expect(client.max_message_size).to eq(Ably::Rest::Client::MAX_MESSAGE_SIZE)
66
+ expect(messages.sum(&:size) < Ably::Rest::Client::MAX_MESSAGE_SIZE).to eq(true)
67
+
63
68
  expect(client).to receive(:post).once.and_call_original
64
69
  expect(channel.publish(messages)).to eql(true)
65
70
  expect(channel.history.items.map(&:name)).to match_array(messages.map { |message| message[:name] })
@@ -68,17 +73,78 @@ describe Ably::Rest::Channel do
68
73
  end
69
74
 
70
75
  context 'with an array of Message objects' do
71
- let(:messages) do
72
- 10.times.map do |index|
73
- Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
76
+ context 'when max_message_size and max_frame_size is not set' do
77
+ before do
78
+ expect(client.max_message_size).to eq(Ably::Rest::Client::MAX_MESSAGE_SIZE)
79
+ expect(client.max_frame_size).to eq(Ably::Rest::Client::MAX_FRAME_SIZE)
80
+ end
81
+
82
+ context 'and messages size (130 bytes) is smaller than the max_message_size' do
83
+ let(:messages) do
84
+ 10.times.map do |index|
85
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
86
+ end
87
+ end
88
+
89
+ it 'publishes an array of messages in one HTTP request' do
90
+ expect(messages.sum &:size).to eq(130)
91
+ expect(client).to receive(:post).once.and_call_original
92
+ expect(channel.publish(messages)).to eql(true)
93
+ expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
94
+ expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
95
+ end
96
+ end
97
+
98
+ context 'and messages size (177784 bytes) is bigger than the max_message_size' do
99
+ let(:messages) do
100
+ 10000.times.map do |index|
101
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 1 })
102
+ end
103
+ end
104
+
105
+ it 'should not publish and raise Ably::Exceptions::MaxMessageSizeExceeded' do
106
+ expect(messages.sum &:size).to eq(177784)
107
+ expect { channel.publish(messages) }.to raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
108
+ end
74
109
  end
75
110
  end
76
111
 
77
- it 'publishes an array of messages in one HTTP request' do
78
- expect(client).to receive(:post).once.and_call_original
79
- expect(channel.publish(messages)).to eql(true)
80
- expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
81
- expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
112
+ context 'when max_message_size is 655 bytes' do
113
+ let(:max_message_size) { 655 }
114
+
115
+ before do
116
+ expect(client.max_message_size).to eq(max_message_size)
117
+ expect(client.max_frame_size).to eq(Ably::Rest::Client::MAX_FRAME_SIZE)
118
+ end
119
+
120
+ context 'and messages size (130 bytes) is smaller than the max_message_size' do
121
+ let(:messages) do
122
+ 10.times.map do |index|
123
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
124
+ end
125
+ end
126
+
127
+ it 'publishes an array of messages in one HTTP request' do
128
+ expect(messages.sum &:size).to eq(130)
129
+ expect(client).to receive(:post).once.and_call_original
130
+ expect(channel.publish(messages)).to eql(true)
131
+ expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
132
+ expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
133
+ end
134
+ end
135
+
136
+ context 'and messages size (177784 bytes) is bigger than the max_message_size' do
137
+ let(:messages) do
138
+ 10000.times.map do |index|
139
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 1 })
140
+ end
141
+ end
142
+
143
+ it 'should not publish and raise Ably::Exceptions::MaxMessageSizeExceeded' do
144
+ expect(messages.sum &:size).to eq(177784)
145
+ expect { channel.publish(messages) }.to raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
146
+ end
147
+ end
82
148
  end
83
149
  end
84
150
 
@@ -350,6 +416,15 @@ describe Ably::Rest::Channel do
350
416
  end
351
417
  end
352
418
  end
419
+
420
+ context 'message size is exceeded (#TO3l8)' do
421
+ let(:data) { 101.times.map { { data: 'x' * 655 } } }
422
+
423
+ it 'should raise Ably::Exceptions::MaxMessageSizeExceeded exception' do
424
+ expect { channel.publish([ data: data ]) }.to \
425
+ raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
426
+ end
427
+ end
353
428
  end
354
429
 
355
430
  describe '#history' do
@@ -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
- it 'overrides the existing channel options and returns the channel object' do
40
- expect(original_channel.options).to_not include(:encrypted)
55
+ it 'overrides the existing channel options and returns the channel object (RSN3c)' do
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,29 +1081,15 @@ describe Ably::Rest::Client do
1081
1081
  end
1082
1082
 
1083
1083
  context 'version headers', :webmock do
1084
- [nil, 'foo'].each do |variant|
1085
- context "with variant #{variant ? variant : 'none'}" do
1086
- if variant
1087
- before do
1088
- Ably.lib_variant = variant
1089
- end
1084
+ [nil, 'ably-ruby/1.1.1 ruby/1.9.3'].each do |agent|
1085
+ context "with #{agent ? "custom #{agent}" : 'default'} agent" do
1086
+ let(:client_options) { default_options.merge(key: api_key, agent: agent) }
1090
1087
 
1091
- after do
1092
- Ably.lib_variant = nil
1093
- end
1094
- end
1095
-
1096
- let(:client_options) { default_options.merge(key: api_key) }
1097
1088
  let!(:publish_message_stub) do
1098
- lib = ['ruby']
1099
- lib << variant if variant
1100
- lib << Ably::VERSION
1101
-
1102
-
1103
1089
  stub_request(:post, "#{client.endpoint}/channels/foo/publish").
1104
1090
  with(headers: {
1105
1091
  'X-Ably-Version' => Ably::PROTOCOL_VERSION,
1106
- 'X-Ably-Lib' => lib.join('-')
1092
+ 'Ably-Agent' => agent || Ably::AGENT
1107
1093
  }).
1108
1094
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
1109
1095
  end
@@ -1111,13 +1097,13 @@ describe Ably::Rest::Client do
1111
1097
  it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
1112
1098
  client.channels.get('foo').publish("event")
1113
1099
  expect(publish_message_stub).to have_been_requested
1114
- expect(Ably::PROTOCOL_VERSION).to eql('1.1')
1100
+ expect(Ably::PROTOCOL_VERSION).to eql('1.2')
1115
1101
  end
1116
1102
  end
1117
1103
  end
1118
1104
  end
1119
1105
 
1120
- context '#request (#RSC19*)' do
1106
+ context '#request (#RSC19*, #TO3l9)' do
1121
1107
  let(:client_options) { default_options.merge(key: api_key) }
1122
1108
  let(:device_id) { random_str }
1123
1109
  let(:endpoint) { client.endpoint }
@@ -1172,6 +1158,12 @@ describe Ably::Rest::Client do
1172
1158
 
1173
1159
  expect(response).to be_success
1174
1160
  end
1161
+
1162
+ it 'raises an exception once body size in bytes exceeded' do
1163
+ expect {
1164
+ client.request(:post, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1165
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1166
+ end
1175
1167
  end
1176
1168
 
1177
1169
  context 'delete', :webmock do
@@ -1201,6 +1193,12 @@ describe Ably::Rest::Client do
1201
1193
 
1202
1194
  expect(response).to be_success
1203
1195
  end
1196
+
1197
+ it 'raises an exception once body size in bytes exceeded' do
1198
+ expect {
1199
+ client.request(:patch, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1200
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1201
+ end
1204
1202
  end
1205
1203
 
1206
1204
  context 'put', :webmock do
@@ -1224,10 +1222,16 @@ describe Ably::Rest::Client do
1224
1222
 
1225
1223
  expect(response).to be_success
1226
1224
  end
1225
+
1226
+ it 'raises an exception once body size in bytes exceeded' do
1227
+ expect {
1228
+ client.request(:put, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1229
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1230
+ end
1227
1231
  end
1228
1232
  end
1229
1233
 
1230
- context 'request_id generation' do
1234
+ context 'request_id generation (#RSC7c)' do
1231
1235
  context 'Timeout error' do
1232
1236
  context 'with option add_request_ids: true and no fallback hosts', :webmock, :prevent_log_stubbing do
1233
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