ably-rest 1.1.2 → 1.2.2

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +1 -1
  4. data/MAINTAINERS.md +1 -0
  5. data/README.md +4 -2
  6. data/ably-rest.gemspec +15 -18
  7. data/lib/ably-rest.rb +2 -0
  8. data/lib/submodules/ably-ruby/.github/workflows/check.yml +50 -0
  9. data/lib/submodules/ably-ruby/CHANGELOG.md +200 -0
  10. data/lib/submodules/ably-ruby/COPYRIGHT +1 -0
  11. data/lib/submodules/ably-ruby/LICENSE +172 -11
  12. data/lib/submodules/ably-ruby/MAINTAINERS.md +1 -0
  13. data/lib/submodules/ably-ruby/README.md +24 -22
  14. data/lib/submodules/ably-ruby/SPEC.md +1020 -929
  15. data/lib/submodules/ably-ruby/UPDATING.md +30 -0
  16. data/lib/submodules/ably-ruby/ably.gemspec +16 -23
  17. data/lib/submodules/ably-ruby/lib/ably/agent.rb +3 -0
  18. data/lib/submodules/ably-ruby/lib/ably/auth.rb +20 -10
  19. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +8 -2
  20. data/lib/submodules/ably-ruby/lib/ably/logger.rb +4 -4
  21. data/lib/submodules/ably-ruby/lib/ably/models/channel_details.rb +59 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/channel_metrics.rb +84 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/channel_occupancy.rb +43 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/channel_options.rb +97 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/channel_status.rb +53 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +8 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/delta_extras.rb +29 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +1 -1
  29. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +6 -2
  30. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +28 -3
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +14 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +31 -14
  34. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +7 -2
  35. data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +11 -1
  37. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +22 -2
  38. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +34 -0
  39. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +19 -7
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_properties.rb +24 -0
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +10 -1
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +6 -0
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +56 -28
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +1 -1
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +9 -0
  48. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +13 -4
  49. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  50. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  51. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +6 -5
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +0 -14
  53. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +44 -29
  54. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +60 -29
  55. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +1 -1
  56. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -1
  57. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +1 -1
  58. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +5 -2
  59. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +1 -1
  60. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +1 -1
  61. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +1 -1
  62. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +1 -1
  63. data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -14
  64. data/lib/submodules/ably-ruby/lib/ably.rb +1 -0
  65. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +4 -4
  66. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  67. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +476 -21
  68. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channels_spec.rb +59 -7
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +72 -16
  70. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +85 -13
  71. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +301 -34
  72. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +77 -0
  73. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +3 -59
  74. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +84 -158
  75. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +3 -19
  76. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +24 -75
  77. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +8 -4
  78. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +141 -10
  79. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +23 -6
  80. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +146 -47
  81. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +61 -3
  82. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +3 -19
  83. data/lib/submodules/ably-ruby/spec/lib/unit/models/channel_options_spec.rb +52 -0
  84. data/lib/submodules/ably-ruby/spec/run_parallel_tests +2 -7
  85. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +131 -8
  86. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +1 -1
  87. data/lib/submodules/ably-ruby/spec/spec_helper.rb +12 -2
  88. data/lib/submodules/ably-ruby/spec/support/serialization_helper.rb +21 -0
  89. data/lib/submodules/ably-ruby/spec/support/test_app.rb +3 -3
  90. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +6 -14
  91. data/lib/submodules/ably-ruby/spec/unit/models/channel_details_spec.rb +30 -0
  92. data/lib/submodules/ably-ruby/spec/unit/models/channel_metrics_spec.rb +42 -0
  93. data/lib/submodules/ably-ruby/spec/unit/models/channel_occupancy_spec.rb +17 -0
  94. data/lib/submodules/ably-ruby/spec/unit/models/channel_status_spec.rb +36 -0
  95. data/lib/submodules/ably-ruby/spec/unit/models/delta_extras_spec.rb +14 -0
  96. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +17 -1
  97. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +97 -0
  98. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +49 -0
  99. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +125 -27
  100. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +14 -0
  101. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +3 -2
  102. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +53 -15
  103. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +19 -6
  104. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  105. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +44 -1
  106. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +81 -14
  107. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +47 -0
  108. data/spec/spec_helper.rb +5 -0
  109. data/spec/unit/client_spec.rb +30 -0
  110. metadata +88 -25
  111. data/lib/submodules/ably-ruby/.travis.yml +0 -19
@@ -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 }
@@ -40,7 +42,7 @@ describe Ably::Rest::Channel do
40
42
 
41
43
  it 'publishes the message without a client_id' do
42
44
  expect(client).to receive(:post).
43
- with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id)).
45
+ with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id), {}).
44
46
  and_return(double('response', status: 201))
45
47
 
46
48
  expect(channel.publish(name, data)).to eql(true)
@@ -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,116 @@ describe Ably::Rest::Channel do
68
73
  end
69
74
 
70
75
  context 'with an array of Message objects' do
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
109
+ end
110
+ end
111
+
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
148
+ end
149
+ end
150
+
151
+ context 'with a Message object' do
152
+ let(:name) { random_str }
153
+
154
+ let(:message) do
155
+ Ably::Models::Message(name: name, data: data)
156
+ end
157
+
158
+ it 'publishes the message' do
159
+ expect(client).to receive(:post).once.and_call_original
160
+ expect(channel.publish(message)).to eql(true)
161
+ expect(channel.history.items.first.name).to eql(name)
162
+ end
163
+ end
164
+
165
+ context 'with a Message object and query params' do
166
+ let(:message) do
167
+ Ably::Models::Message(name: name, data: data)
168
+ end
169
+
170
+ it 'should fail to publish the message (RSL1l1)' do
171
+ expect(client).to receive(:post).once.and_call_original
172
+ expect { channel.publish(message, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
173
+ end
174
+ end
175
+
176
+ context 'with Messages and query params' do
71
177
  let(:messages) do
72
178
  10.times.map do |index|
73
- Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
179
+ { name: index.to_s, data: { "index" => index + 10 } }
74
180
  end
75
181
  end
76
182
 
77
- it 'publishes an array of messages in one HTTP request' do
183
+ it 'should fail to publish the message (RSL1l1)' do
78
184
  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))
185
+ expect { channel.publish(messages, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
82
186
  end
83
187
  end
84
188
 
@@ -96,7 +200,7 @@ describe Ably::Rest::Channel do
96
200
  let(:data) { random_str }
97
201
 
98
202
  it 'publishes the message without a name attribute in the payload' do
99
- expect(client).to receive(:post).with(anything, { "data" => data }).once.and_call_original
203
+ expect(client).to receive(:post).with(anything, { "data" => data }, {}).once.and_call_original
100
204
  expect(channel.publish(nil, data)).to eql(true)
101
205
  expect(channel.history.items.first.name).to be_nil
102
206
  expect(channel.history.items.first.data).to eql(data)
@@ -107,7 +211,7 @@ describe Ably::Rest::Channel do
107
211
  let(:name) { random_str }
108
212
 
109
213
  it 'publishes the message without a data attribute in the payload' do
110
- expect(client).to receive(:post).with(anything, { "name" => name }).once.and_call_original
214
+ expect(client).to receive(:post).with(anything, { "name" => name }, {}).once.and_call_original
111
215
  expect(channel.publish(name)).to eql(true)
112
216
  expect(channel.history.items.first.name).to eql(name)
113
217
  expect(channel.history.items.first.data).to be_nil
@@ -118,7 +222,7 @@ describe Ably::Rest::Channel do
118
222
  let(:name) { random_str }
119
223
 
120
224
  it 'publishes the message without any attributes in the payload' do
121
- expect(client).to receive(:post).with(anything, {}).once.and_call_original
225
+ expect(client).to receive(:post).with(anything, {}, {}).once.and_call_original
122
226
  expect(channel.publish(nil)).to eql(true)
123
227
  expect(channel.history.items.first.name).to be_nil
124
228
  expect(channel.history.items.first.data).to be_nil
@@ -312,6 +416,15 @@ describe Ably::Rest::Channel do
312
416
  end
313
417
  end
314
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
315
428
  end
316
429
 
317
430
  describe '#history' do
@@ -482,5 +595,23 @@ describe Ably::Rest::Channel do
482
595
  expect(channel.presence).to be_a(Ably::Rest::Presence)
483
596
  end
484
597
  end
598
+
599
+ context '#status' do
600
+ let(:channel_name) { "persisted:#{random_str(4)}" }
601
+ let(:channel) { client.channel(channel_name) }
602
+ let(:channel_details) { channel.status }
603
+
604
+ it 'should return channel details status (#RSL8, #RSL8a)' do
605
+ expect(channel_details.channel_id).to eq(channel_name)
606
+ expect(channel_details.name).to eq(channel_name)
607
+ expect(channel_details.status).to be_a(Ably::Models::ChannelStatus)
608
+ expect(channel_details.status.is_active).to eq(true)
609
+ expect(channel_details.status.occupancy.metrics.publishers).to eq(0)
610
+ expect(channel_details.status.occupancy.metrics.subscribers).to eq(0)
611
+ expect(channel_details.status.occupancy.metrics.presence_connections).to eq(0)
612
+ expect(channel_details.status.occupancy.metrics.presence_members).to eq(0)
613
+ expect(channel_details.status.occupancy.metrics.presence_subscribers).to eq(0)
614
+ end
615
+ end
485
616
  end
486
617
  end
@@ -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
 
@@ -12,7 +12,7 @@ describe Ably::Rest::Client do
12
12
  http_defaults = Ably::Rest::Client::HTTP_DEFAULTS
13
13
 
14
14
  def encode64(text)
15
- Base64.encode64(text).gsub("\n", '')
15
+ Base64.urlsafe_encode64(text)
16
16
  end
17
17
 
18
18
  context '#initialize' do
@@ -56,14 +56,6 @@ describe Ably::Rest::Client do
56
56
  end
57
57
  end
58
58
 
59
- context 'with a :client_id configured' do
60
- let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: random_str)) }
61
-
62
- it 'uses token authentication' do
63
- expect(client.auth).to be_using_token_auth
64
- end
65
- end
66
-
67
59
  context 'with a non string :client_id' do
68
60
  let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: 1)) }
69
61
 
@@ -144,11 +136,12 @@ describe Ably::Rest::Client do
144
136
  let(:history_querystring) { history_params.map { |k, v| "#{k}=#{v}" }.join("&") }
145
137
 
146
138
  context 'with basic auth', webmock: true do
147
- let(:client_options) { default_options.merge(key: api_key) }
139
+ let(:client_options) { default_options.merge(key: api_key, client_id: client_id) }
148
140
 
149
141
  let!(:get_message_history_stub) do
150
- stub_request(:get, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}").
151
- to_return(body: [], headers: { 'Content-Type' => 'application/json' })
142
+ stub_request(:get, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}")
143
+ .with(headers: { 'X-Ably-ClientId' => encode64(client_id) })
144
+ .to_return(body: [], headers: { 'Content-Type' => 'application/json' })
152
145
  end
153
146
 
154
147
  it 'sends the API key in authentication part of the secure URL (the Authorization: Basic header is not used with the Faraday HTTP library by default)' do
@@ -308,30 +301,44 @@ describe Ably::Rest::Client do
308
301
  context 'configured' do
309
302
  let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
310
303
 
311
- it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com (#RSC15a)' do
304
+ it 'should make connection attempts to a.ably-realtime.com, b.ably-realtime.com, c.ably-realtime.com, d.ably-realtime.com, e.ably-realtime.com (#RSC15a)' do
312
305
  hosts = []
313
306
  5.times do
314
307
  hosts << client.fallback_connection.host
315
308
  end
316
- expect(hosts).to match_array(%w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com))
309
+ expect(hosts).to match_array(%w(a.ably-realtime.com b.ably-realtime.com c.ably-realtime.com d.ably-realtime.com e.ably-realtime.com))
317
310
  end
318
311
  end
319
312
 
320
313
  context 'when environment is NOT production (#RSC15b)' do
321
- let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) }
322
- let!(:default_host_request_stub) do
323
- stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
324
- raise Faraday::TimeoutError.new('timeout error message')
314
+ context 'and custom fallback hosts are empty' do
315
+ let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key, fallback_hosts: []) }
316
+ let!(:default_host_request_stub) do
317
+ stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
318
+ raise Faraday::TimeoutError.new('timeout error message')
319
+ end
320
+ end
321
+
322
+ it 'does not retry failed requests with fallback hosts when there is a connection error' do
323
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
325
324
  end
326
325
  end
327
326
 
328
- it 'does not retry failed requests with fallback hosts when there is a connection error' do
329
- expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
327
+ context 'and no custom fallback hosts are provided' do
328
+ let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) }
329
+
330
+ it 'should make connection attempts to sandbox-a-fallback.ably-realtime.com, sandbox-b-fallback.ably-realtime.com, sandbox-c-fallback.ably-realtime.com, sandbox-d-fallback.ably-realtime.com, sandbox-e-fallback.ably-realtime.com (#RSC15a)' do
331
+ hosts = []
332
+ 5.times do
333
+ hosts << client.fallback_connection.host
334
+ end
335
+ expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox-#{id}-fallback.ably-realtime.com" })
336
+ end
330
337
  end
331
338
  end
332
339
 
333
340
  context 'when environment is production' do
334
- let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
341
+ let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) }
335
342
  let(:max_retry_count) { 2 }
336
343
  let(:max_retry_duration) { 0.5 }
337
344
  let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
@@ -830,11 +837,12 @@ describe Ably::Rest::Client do
830
837
  end
831
838
 
832
839
  context 'when environment is not production and server returns a 50x error' do
840
+ let(:env) { 'custom-env' }
841
+ let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}-#{id}-fallback.ably-realtime.com" } }
833
842
  let(:custom_hosts) { %w(A.foo.com B.foo.com) }
834
843
  let(:max_retry_count) { 2 }
835
844
  let(:max_retry_duration) { 0.5 }
836
845
  let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
837
- let(:env) { 'custom-env' }
838
846
  let(:production_options) do
839
847
  default_options.merge(
840
848
  environment: env,
@@ -858,6 +866,26 @@ describe Ably::Rest::Client do
858
866
  stub_request(:post, "https://#{env}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
859
867
  end
860
868
 
869
+ context 'with no fallback hosts provided (#TBC, see https://github.com/ably/wiki/issues/361)' do
870
+ let(:client_options) {
871
+ production_options.merge(log_level: :fatal)
872
+ }
873
+
874
+ it 'uses the default fallback hosts for that environment as this is not an authentication failure' do
875
+ fallbacks_called_count = 0
876
+ default_fallbacks.each do |host|
877
+ counting_fallback_proc = proc do
878
+ fallbacks_called_count += 1
879
+ fallback_block.call
880
+ end
881
+ stub_request(:post, "https://#{host}#{path}").to_return(&counting_fallback_proc)
882
+ end
883
+ expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
884
+ expect(default_host_request_stub).to have_been_requested
885
+ expect(fallbacks_called_count).to be >= 2
886
+ end
887
+ end
888
+
861
889
  context 'with custom fallback hosts provided (#RSC15b, #TO3k6)' do
862
890
  let!(:first_fallback_request_stub) do
863
891
  stub_request(:post, "https://#{custom_hosts[0]}#{path}").to_return(&fallback_block)
@@ -1053,29 +1081,15 @@ describe Ably::Rest::Client do
1053
1081
  end
1054
1082
 
1055
1083
  context 'version headers', :webmock do
1056
- [nil, 'foo'].each do |variant|
1057
- context "with variant #{variant ? variant : 'none'}" do
1058
- if variant
1059
- before do
1060
- Ably.lib_variant = variant
1061
- end
1084
+ [nil, 'ably-ruby/1.1.1 ruby/3.1.1'].each do |agent|
1085
+ context "with #{agent ? "custom #{agent}" : 'default'} agent" do
1086
+ let(:client_options) { default_options.merge(key: api_key, agent: agent) }
1062
1087
 
1063
- after do
1064
- Ably.lib_variant = nil
1065
- end
1066
- end
1067
-
1068
- let(:client_options) { default_options.merge(key: api_key) }
1069
1088
  let!(:publish_message_stub) do
1070
- lib = ['ruby']
1071
- lib << variant if variant
1072
- lib << Ably::VERSION
1073
-
1074
-
1075
1089
  stub_request(:post, "#{client.endpoint}/channels/foo/publish").
1076
1090
  with(headers: {
1077
1091
  'X-Ably-Version' => Ably::PROTOCOL_VERSION,
1078
- 'X-Ably-Lib' => lib.join('-')
1092
+ 'Ably-Agent' => agent || Ably::AGENT
1079
1093
  }).
1080
1094
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
1081
1095
  end
@@ -1083,14 +1097,16 @@ describe Ably::Rest::Client do
1083
1097
  it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
1084
1098
  client.channels.get('foo').publish("event")
1085
1099
  expect(publish_message_stub).to have_been_requested
1086
- expect(Ably::PROTOCOL_VERSION).to eql('1.1')
1100
+ expect(Ably::PROTOCOL_VERSION).to eql('1.2')
1087
1101
  end
1088
1102
  end
1089
1103
  end
1090
1104
  end
1091
1105
 
1092
- context '#request (#RSC19*)' do
1106
+ context '#request (#RSC19*, #TO3l9)' do
1093
1107
  let(:client_options) { default_options.merge(key: api_key) }
1108
+ let(:device_id) { random_str }
1109
+ let(:endpoint) { client.endpoint }
1094
1110
 
1095
1111
  context 'get' do
1096
1112
  it 'returns an HttpPaginatedResponse object' do
@@ -1130,13 +1146,96 @@ describe Ably::Rest::Client do
1130
1146
  end
1131
1147
  end
1132
1148
  end
1149
+
1150
+ context 'post', :webmock do
1151
+ before do
1152
+ stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
1153
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1154
+ end
1155
+
1156
+ it 'supports post' do
1157
+ response = client.request(:delete, "push/deviceRegistrations/#{device_id}/resetUpdateToken")
1158
+
1159
+ expect(response).to be_success
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
1167
+ end
1168
+
1169
+ context 'delete', :webmock do
1170
+ before do
1171
+ stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}").
1172
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1173
+ end
1174
+
1175
+ it 'supports delete' do
1176
+ response = client.request(:delete, "/push/channelSubscriptions", { deviceId: device_id})
1177
+
1178
+ expect(response).to be_success
1179
+ end
1180
+ end
1181
+
1182
+ context 'patch', :webmock do
1183
+ let(:body_params) { { 'metadata' => { 'key' => 'value' } } }
1184
+
1185
+ before do
1186
+ stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}")
1187
+ .with(body: serialize_body(body_params, protocol))
1188
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1189
+ end
1190
+
1191
+ it 'supports patch' do
1192
+ response = client.request(:patch, "/push/deviceRegistrations/#{device_id}", {}, body_params)
1193
+
1194
+ expect(response).to be_success
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
1202
+ end
1203
+
1204
+ context 'put', :webmock do
1205
+ let(:body_params) do
1206
+ {
1207
+ 'id' => random_str,
1208
+ 'platform' => 'ios',
1209
+ 'formFactor' => 'phone',
1210
+ 'metadata' => { 'key' => 'value' }
1211
+ }
1212
+ end
1213
+
1214
+ before do
1215
+ stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}")
1216
+ .with(body: serialize_body(body_params, protocol))
1217
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1218
+ end
1219
+
1220
+ it 'supports put' do
1221
+ response = client.request(:put, "/push/deviceRegistrations/#{device_id}", {}, body_params)
1222
+
1223
+ expect(response).to be_success
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
1231
+ end
1133
1232
  end
1134
1233
 
1135
- context 'request_id generation' do
1234
+ context 'request_id generation (#RSC7c)' do
1136
1235
  context 'Timeout error' do
1137
- context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
1236
+ context 'with option add_request_ids: true and no fallback hosts', :webmock, :prevent_log_stubbing do
1138
1237
  let(:custom_logger_object) { TestLogger.new }
1139
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
1238
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true, fallback_hosts: []) }
1140
1239
 
1141
1240
  before do
1142
1241
  @request_id = nil
@@ -1226,8 +1325,8 @@ describe Ably::Rest::Client do
1226
1325
  end
1227
1326
  end
1228
1327
 
1229
- context 'without request_id' do
1230
- let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1328
+ context 'without request_id and no fallback hosts' do
1329
+ let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0, fallback_hosts: []) }
1231
1330
 
1232
1331
  it 'does not include request_id in ConnectionTimeout error' do
1233
1332
  begin
@@ -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