ably-rest 0.8.2 → 0.8.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -43
- data/SPEC.md +707 -580
- data/lib/submodules/ably-ruby/.travis.yml +1 -0
- data/lib/submodules/ably-ruby/CHANGELOG.md +143 -3
- data/lib/submodules/ably-ruby/README.md +1 -1
- data/lib/submodules/ably-ruby/SPEC.md +842 -520
- data/lib/submodules/ably-ruby/ably.gemspec +1 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +114 -87
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +40 -14
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +3 -5
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_result.rb +3 -12
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +8 -2
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -3
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +7 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +191 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +97 -25
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +11 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +22 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +73 -40
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +48 -33
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +17 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +43 -16
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +57 -26
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +3 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +242 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +277 -5
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channels_spec.rb +64 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +26 -5
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +23 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +167 -16
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +9 -8
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +1 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +121 -10
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +13 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +161 -79
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +3 -3
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +142 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +180 -18
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +8 -8
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +136 -25
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +60 -4
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +54 -3
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +7 -6
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +1 -9
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_result_spec.rb +1 -18
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +21 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +10 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +27 -8
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +0 -8
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +7 -7
- metadata +5 -2
@@ -130,7 +130,7 @@ describe Ably::Rest do
|
|
130
130
|
if [1, 3].include?(@publish_attempts)
|
131
131
|
{ status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } }
|
132
132
|
else
|
133
|
-
raise Ably::Exceptions::
|
133
|
+
raise Ably::Exceptions::TokenExpired.new('Authentication failure', 401, 40140)
|
134
134
|
end
|
135
135
|
end
|
136
136
|
end
|
@@ -155,10 +155,10 @@ describe Ably::Rest do
|
|
155
155
|
context 'when NOT auth#token_renewable?' do
|
156
156
|
let(:client) { Ably::Rest::Client.new(token: 'token ID cannot be used to create a new token', environment: environment, protocol: protocol) }
|
157
157
|
|
158
|
-
it 'should raise an
|
158
|
+
it 'should raise an TokenExpired exception' do
|
159
159
|
client.channel(channel).publish('evt', 'msg')
|
160
160
|
expect(@publish_attempts).to eql(1)
|
161
|
-
expect { client.channel(channel).publish('evt', 'msg') }.to raise_error Ably::Exceptions::
|
161
|
+
expect { client.channel(channel).publish('evt', 'msg') }.to raise_error Ably::Exceptions::TokenExpired
|
162
162
|
expect(@token_requests).to eql(0)
|
163
163
|
end
|
164
164
|
end
|
@@ -5,17 +5,97 @@ 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} }
|
9
|
+
let(:client_options) { default_options }
|
8
10
|
let(:client) do
|
9
|
-
Ably::Rest::Client.new(
|
11
|
+
Ably::Rest::Client.new(client_options)
|
10
12
|
end
|
11
13
|
|
12
14
|
describe '#publish' do
|
13
|
-
let(:channel) { client.channel(
|
14
|
-
let(:
|
15
|
-
let(:
|
15
|
+
let(:channel) { client.channel(random_str) }
|
16
|
+
let(:name) { 'foo' }
|
17
|
+
let(:data) { 'woop!' }
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
context 'with name and data arguments' do
|
20
|
+
it 'publishes the message and return true indicating success' do
|
21
|
+
expect(channel.publish(name, data)).to eql(true)
|
22
|
+
expect(channel.history.items.first.name).to eql(name)
|
23
|
+
expect(channel.history.items.first.data).to eql(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with an array of Hash objects with :name and :data attributes' do
|
28
|
+
let(:messages) do
|
29
|
+
10.times.map do |index|
|
30
|
+
{ name: index.to_s, data: { "index" => index + 10 } }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'publishes an array of messages in one HTTP request' do
|
35
|
+
expect(client).to receive(:post).once.and_call_original
|
36
|
+
expect(channel.publish(messages)).to eql(true)
|
37
|
+
expect(channel.history.items.map(&:name)).to match_array(messages.map { |message| message[:name] })
|
38
|
+
expect(channel.history.items.map(&:data)).to match_array(messages.map { |message| message[:data] })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with an array of Message objects' do
|
43
|
+
let(:messages) do
|
44
|
+
10.times.map do |index|
|
45
|
+
Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'publishes an array of messages in one HTTP request' do
|
50
|
+
expect(client).to receive(:post).once.and_call_original
|
51
|
+
expect(channel.publish(messages)).to eql(true)
|
52
|
+
expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
|
53
|
+
expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'without adequate permissions on the channel' do
|
58
|
+
let(:capability) { { onlyChannel: ['subscribe'] } }
|
59
|
+
let(:client_options) { default_options.merge(use_token_auth: true, token_params: { capability: capability }) }
|
60
|
+
|
61
|
+
it 'raises a permission error when publishing' do
|
62
|
+
expect { channel.publish(name, data) }.to raise_error(Ably::Exceptions::InvalidRequest, /not permitted/)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'null attributes' do
|
67
|
+
context 'when name is null' do
|
68
|
+
let(:data) { random_str }
|
69
|
+
|
70
|
+
it 'publishes the message without a name attribute in the payload' do
|
71
|
+
expect(client).to receive(:post).with(anything, { "data" => data }).once.and_call_original
|
72
|
+
expect(channel.publish(nil, data)).to eql(true)
|
73
|
+
expect(channel.history.items.first.name).to be_nil
|
74
|
+
expect(channel.history.items.first.data).to eql(data)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when data is null' do
|
79
|
+
let(:name) { random_str }
|
80
|
+
|
81
|
+
it 'publishes the message without a data attribute in the payload' do
|
82
|
+
expect(client).to receive(:post).with(anything, { "name" => name }).once.and_call_original
|
83
|
+
expect(channel.publish(name)).to eql(true)
|
84
|
+
expect(channel.history.items.first.name).to eql(name)
|
85
|
+
expect(channel.history.items.first.data).to be_nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with neither name or data attributes' do
|
90
|
+
let(:name) { random_str }
|
91
|
+
|
92
|
+
it 'publishes the message without any attributes in the payload' do
|
93
|
+
expect(client).to receive(:post).with(anything, {}).once.and_call_original
|
94
|
+
expect(channel.publish(nil)).to eql(true)
|
95
|
+
expect(channel.history.items.first.name).to be_nil
|
96
|
+
expect(channel.history.items.first.data).to be_nil
|
97
|
+
end
|
98
|
+
end
|
19
99
|
end
|
20
100
|
end
|
21
101
|
|
@@ -36,7 +116,11 @@ describe Ably::Rest::Channel do
|
|
36
116
|
end
|
37
117
|
end
|
38
118
|
|
39
|
-
it '
|
119
|
+
it 'returns a PaginatedResult model' do
|
120
|
+
expect(channel.history).to be_a(Ably::Models::PaginatedResult)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns the current message history for the channel' do
|
40
124
|
actual_history_items = channel.history.items
|
41
125
|
|
42
126
|
expect(actual_history_items.size).to eql(3)
|
@@ -49,7 +133,7 @@ describe Ably::Rest::Channel do
|
|
49
133
|
end
|
50
134
|
|
51
135
|
context 'message timestamps' do
|
52
|
-
|
136
|
+
specify 'are after the messages were published' do
|
53
137
|
channel.history.items.each do |message|
|
54
138
|
expect(before_published.to_f).to be < message.timestamp.to_f
|
55
139
|
end
|
@@ -57,14 +141,14 @@ describe Ably::Rest::Channel do
|
|
57
141
|
end
|
58
142
|
|
59
143
|
context 'message IDs' do
|
60
|
-
it '
|
144
|
+
it 'is unique' do
|
61
145
|
message_ids = channel.history.items.map(&:id).compact
|
62
146
|
expect(message_ids.count).to eql(3)
|
63
147
|
expect(message_ids.uniq.count).to eql(3)
|
64
148
|
end
|
65
149
|
end
|
66
150
|
|
67
|
-
it '
|
151
|
+
it 'returns paged history using the PaginatedResult model' do
|
68
152
|
page_1 = channel.history(limit: 1)
|
69
153
|
page_2 = page_1.next
|
70
154
|
page_3 = page_2.next
|
@@ -74,17 +158,42 @@ describe Ably::Rest::Channel do
|
|
74
158
|
|
75
159
|
expect(page_1.items.size).to eql(1)
|
76
160
|
expect(page_1).to_not be_last
|
77
|
-
expect(page_1).to be_first
|
78
161
|
|
79
162
|
# Page 2
|
80
163
|
expect(page_2.items.size).to eql(1)
|
81
164
|
expect(page_2).to_not be_last
|
82
|
-
expect(page_2).to_not be_first
|
83
165
|
|
84
166
|
# Page 3
|
85
167
|
expect(page_3.items.size).to eql(1)
|
86
168
|
expect(page_3).to be_last
|
87
|
-
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'direction' do
|
172
|
+
it 'returns paged history backwards by default' do
|
173
|
+
items = channel.history.items
|
174
|
+
expect(items.first.name).to eql(expected_history.last.fetch(:name))
|
175
|
+
expect(items.last.name).to eql(expected_history.first.fetch(:name))
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns history forward if specified in the options' do
|
179
|
+
items = channel.history(direction: :forwards).items
|
180
|
+
expect(items.first.name).to eql(expected_history.first.fetch(:name))
|
181
|
+
expect(items.last.name).to eql(expected_history.last.fetch(:name))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'limit' do
|
186
|
+
before do
|
187
|
+
channel.publish 120.times.to_a.map { |i| { name: 'event' } }
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'defaults to 100' do
|
191
|
+
page = channel.history
|
192
|
+
expect(page.items.count).to eql(100)
|
193
|
+
next_page = page.next
|
194
|
+
expect(next_page.items.count).to be >= 20
|
195
|
+
expect(next_page.items.count).to be < 100
|
196
|
+
end
|
88
197
|
end
|
89
198
|
end
|
90
199
|
|
@@ -97,7 +206,7 @@ describe Ably::Rest::Channel do
|
|
97
206
|
client_end_point.password = key_secret
|
98
207
|
end
|
99
208
|
end
|
100
|
-
let(:
|
209
|
+
let(:default_history_options) do
|
101
210
|
{
|
102
211
|
direction: :backwards,
|
103
212
|
limit: 100
|
@@ -107,7 +216,8 @@ describe Ably::Rest::Channel do
|
|
107
216
|
[:start, :end].each do |option|
|
108
217
|
describe ":#{option}", :webmock do
|
109
218
|
let!(:history_stub) {
|
110
|
-
query_params =
|
219
|
+
query_params = default_history_options
|
220
|
+
.merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
|
111
221
|
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{query_params}").
|
112
222
|
to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
|
113
223
|
}
|
@@ -136,6 +246,23 @@ describe Ably::Rest::Channel do
|
|
136
246
|
end
|
137
247
|
end
|
138
248
|
end
|
249
|
+
|
250
|
+
context 'when argument start is after end' do
|
251
|
+
let(:subject) { channel.history(start: as_since_epoch(Time.now), end: Time.now - 120) }
|
252
|
+
|
253
|
+
it 'should raise an exception' do
|
254
|
+
expect { subject.items }.to raise_error ArgumentError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context '#presence' do
|
260
|
+
let(:channel_name) { "persisted:#{random_str(4)}" }
|
261
|
+
let(:channel) { client.channel(channel_name) }
|
262
|
+
|
263
|
+
it 'returns a REST Presence object' do
|
264
|
+
expect(channel.presence).to be_a(Ably::Rest::Presence)
|
265
|
+
end
|
139
266
|
end
|
140
267
|
end
|
141
268
|
end
|
@@ -32,6 +32,29 @@ describe Ably::Rest::Channels do
|
|
32
32
|
it_behaves_like 'a channel'
|
33
33
|
end
|
34
34
|
|
35
|
+
describe 'accessing an existing channel object with different options' do
|
36
|
+
let(:new_channel_options) { { encrypted: true } }
|
37
|
+
let(:original_channel) { client.channels.get(channel_name, options) }
|
38
|
+
|
39
|
+
it 'overrides the existing channel options and returns the channel object' do
|
40
|
+
expect(original_channel.options).to_not include(:encrypted)
|
41
|
+
new_channel = client.channels.get(channel_name, new_channel_options)
|
42
|
+
expect(new_channel).to be_a(Ably::Rest::Channel)
|
43
|
+
expect(new_channel.options[:encrypted]).to eql(true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'accessing an existing channel object without specifying any channel options' do
|
48
|
+
let(:original_channel) { client.channels.get(channel_name, options) }
|
49
|
+
|
50
|
+
it 'returns the existing channel without modifying the channel options' do
|
51
|
+
expect(original_channel.options).to eql(options)
|
52
|
+
new_channel = client.channels.get(channel_name)
|
53
|
+
expect(new_channel).to be_a(Ably::Rest::Channel)
|
54
|
+
expect(original_channel.options).to eql(options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
35
58
|
describe 'using undocumented array accessor [] method on client#channels' do
|
36
59
|
let(:channel) { client.channels[channel_name] }
|
37
60
|
let(:channel_with_options) { client.channels[channel_name, options] }
|
@@ -10,30 +10,128 @@ describe Ably::Rest::Client do
|
|
10
10
|
|
11
11
|
connection_retry = Ably::Rest::Client::CONNECTION_RETRY
|
12
12
|
|
13
|
+
def encode64(text)
|
14
|
+
Base64.encode64(text).gsub("\n", '')
|
15
|
+
end
|
16
|
+
|
13
17
|
context '#initialize' do
|
14
18
|
let(:client_id) { random_str }
|
15
19
|
let(:token_request) { client.auth.create_token_request(key_name: key_name, key_secret: key_secret, client_id: client_id) }
|
16
20
|
|
17
|
-
context 'with
|
21
|
+
context 'with only an API key' do
|
22
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key)) }
|
23
|
+
|
24
|
+
it 'uses basic authentication' do
|
25
|
+
expect(client.auth).to be_using_basic_auth
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with an explicit string :token' do
|
30
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(token: random_str)) }
|
31
|
+
|
32
|
+
it 'uses token authentication' do
|
33
|
+
expect(client.auth).to be_using_token_auth
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with :use_token_auth set to true' do
|
38
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, use_token_auth: true)) }
|
39
|
+
|
40
|
+
it 'uses token authentication' do
|
41
|
+
expect(client.auth).to be_using_token_auth
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with a :client_id configured' do
|
46
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: random_str)) }
|
47
|
+
|
48
|
+
it 'uses token authentication' do
|
49
|
+
expect(client.auth).to be_using_token_auth
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'with an :auth_callback Proc' do
|
18
54
|
let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
|
19
55
|
|
20
56
|
it 'calls the auth Proc to get a new token' do
|
21
57
|
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
|
22
58
|
expect(client.auth.current_token_details.client_id).to eql(client_id)
|
23
59
|
end
|
60
|
+
|
61
|
+
it 'uses token authentication' do
|
62
|
+
expect(client.auth).to be_using_token_auth
|
63
|
+
end
|
24
64
|
end
|
25
65
|
|
26
66
|
context 'with an auth URL' do
|
27
|
-
let(:client_options)
|
67
|
+
let(:client_options) { default_options.merge(key: api_key, auth_url: token_request_url, auth_method: :get) }
|
28
68
|
let(:token_request_url) { 'http://get.token.request.com/' }
|
29
69
|
|
30
|
-
|
31
|
-
|
70
|
+
it 'uses token authentication' do
|
71
|
+
expect(client.auth).to be_using_token_auth
|
32
72
|
end
|
33
73
|
|
34
|
-
|
35
|
-
|
36
|
-
|
74
|
+
context 'before any REST request' do
|
75
|
+
before do
|
76
|
+
expect(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, hash_including(:auth_method => :get)).once do
|
77
|
+
client.auth.create_token_request(token_params: { client_id: client_id })
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'sends an HTTP request to the provided auth URL to get a new token' do
|
82
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
|
83
|
+
expect(client.auth.current_token_details.client_id).to eql(client_id)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'auth headers', webmock: true do
|
89
|
+
let(:channel_name) { random_str }
|
90
|
+
let(:history_params) { { 'direction' => 'backwards', 'limit' => 100 } }
|
91
|
+
let(:history_querystring) { history_params.map { |k, v| "#{k}=#{v}" }.join("&") }
|
92
|
+
|
93
|
+
context 'with basic auth', webmock: true do
|
94
|
+
let(:client_options) { default_options.merge(key: api_key) }
|
95
|
+
|
96
|
+
let!(:get_message_history_stub) do
|
97
|
+
stub_request(:get, "https://#{api_key}@#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}").
|
98
|
+
to_return(body: [], headers: { 'Content-Type' => 'application/json' })
|
99
|
+
end
|
100
|
+
|
101
|
+
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
|
102
|
+
client.channel(channel_name).history history_params
|
103
|
+
expect(get_message_history_stub).to have_been_requested
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'with token auth', webmock: true do
|
108
|
+
let(:token_string) { random_str }
|
109
|
+
let(:client_options) { default_options.merge(token: token_string) }
|
110
|
+
|
111
|
+
let!(:get_message_history_stub) do
|
112
|
+
stub_request(:get, "#{http_protocol}://#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}").
|
113
|
+
with(headers: { 'Authorization' => "Bearer #{encode64(token_string)}" }).
|
114
|
+
to_return(body: [], headers: { 'Content-Type' => 'application/json' })
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'without specifying protocol' do
|
118
|
+
let(:http_protocol) { 'https' }
|
119
|
+
|
120
|
+
it 'sends the token string over HTTPS in the Authorization Bearer header with Base64 encoding' do
|
121
|
+
client.channel(channel_name).history history_params
|
122
|
+
expect(get_message_history_stub).to have_been_requested
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when setting constructor ClientOption :tls to false' do
|
127
|
+
let(:client_options) { default_options.merge(token: token_string, tls: false) }
|
128
|
+
let(:http_protocol) { 'http' }
|
129
|
+
|
130
|
+
it 'sends the token string over HTTP in the Authorization Bearer header with Base64 encoding' do
|
131
|
+
client.channel(channel_name).history history_params
|
132
|
+
expect(get_message_history_stub).to have_been_requested
|
133
|
+
end
|
134
|
+
end
|
37
135
|
end
|
38
136
|
end
|
39
137
|
end
|
@@ -53,7 +151,12 @@ describe Ably::Rest::Client do
|
|
53
151
|
let(:token_request_next) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
|
54
152
|
|
55
153
|
context 'when expired' do
|
56
|
-
|
154
|
+
before do
|
155
|
+
# Ensure tokens issued expire immediately after issue
|
156
|
+
stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0)
|
157
|
+
end
|
158
|
+
|
159
|
+
let(:token_request_options) { { key_name: key_name, key_secret: key_secret, token_params: { ttl: Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER } } }
|
57
160
|
|
58
161
|
it 'creates a new token automatically when the old token expires' do
|
59
162
|
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
|
@@ -130,7 +233,7 @@ describe Ably::Rest::Client do
|
|
130
233
|
end
|
131
234
|
|
132
235
|
it 'does not retry failed requests with fallback hosts when there is a connection error' do
|
133
|
-
expect { publish_block.call }.to raise_error Ably::Exceptions::
|
236
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
|
134
237
|
end
|
135
238
|
end
|
136
239
|
|
@@ -139,6 +242,7 @@ describe Ably::Rest::Client do
|
|
139
242
|
let(:max_attempts) { 2 }
|
140
243
|
let(:cumulative_timeout) { 0.5 }
|
141
244
|
let(:client_options) { default_options.merge(environment: nil, key: api_key) }
|
245
|
+
let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
|
142
246
|
|
143
247
|
before do
|
144
248
|
stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
|
@@ -151,15 +255,11 @@ describe Ably::Rest::Client do
|
|
151
255
|
end
|
152
256
|
|
153
257
|
let!(:first_fallback_request_stub) do
|
154
|
-
stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return
|
155
|
-
raise Faraday::SSLError.new('ssl error message')
|
156
|
-
end
|
258
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return(&fallback_block)
|
157
259
|
end
|
158
260
|
|
159
261
|
let!(:second_fallback_request_stub) do
|
160
|
-
stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return
|
161
|
-
raise Faraday::SSLError.new('ssl error message')
|
162
|
-
end
|
262
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return(&fallback_block)
|
163
263
|
end
|
164
264
|
|
165
265
|
context 'and connection times out' do
|
@@ -185,7 +285,7 @@ describe Ably::Rest::Client do
|
|
185
285
|
end
|
186
286
|
|
187
287
|
it 'makes no further attempts to any fallback hosts' do
|
188
|
-
expect { publish_block.call }.to raise_error Ably::Exceptions::
|
288
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
|
189
289
|
expect(default_host_request_stub).to have_been_requested
|
190
290
|
expect(first_fallback_request_stub).to_not have_been_requested
|
191
291
|
expect(second_fallback_request_stub).to_not have_been_requested
|
@@ -207,6 +307,52 @@ describe Ably::Rest::Client do
|
|
207
307
|
expect(second_fallback_request_stub).to have_been_requested
|
208
308
|
end
|
209
309
|
end
|
310
|
+
|
311
|
+
context 'and basic authentication fails' do
|
312
|
+
let(:status) { 401 }
|
313
|
+
let!(:default_host_request_stub) do
|
314
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return(
|
315
|
+
headers: { 'Content-Type' => 'application/json' },
|
316
|
+
status: status,
|
317
|
+
body: {
|
318
|
+
"error" => {
|
319
|
+
"statusCode" => 401,
|
320
|
+
"code" => 40101,
|
321
|
+
"message" => "Invalid credentials"
|
322
|
+
}
|
323
|
+
}.to_json
|
324
|
+
)
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'does not attempt the fallback hosts as this is an authentication failure' do
|
328
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::InvalidRequest)
|
329
|
+
expect(default_host_request_stub).to have_been_requested
|
330
|
+
expect(first_fallback_request_stub).to_not have_been_requested
|
331
|
+
expect(second_fallback_request_stub).to_not have_been_requested
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context 'and server returns a 50x error' do
|
336
|
+
let(:status) { 502 }
|
337
|
+
let(:fallback_block) do
|
338
|
+
Proc.new do
|
339
|
+
{
|
340
|
+
headers: { 'Content-Type' => 'text/html' },
|
341
|
+
status: status
|
342
|
+
}
|
343
|
+
end
|
344
|
+
end
|
345
|
+
let!(:default_host_request_stub) do
|
346
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'attempts the fallback hosts as this is an authentication failure' do
|
350
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
351
|
+
expect(default_host_request_stub).to have_been_requested
|
352
|
+
expect(first_fallback_request_stub).to have_been_requested
|
353
|
+
expect(second_fallback_request_stub).to have_been_requested
|
354
|
+
end
|
355
|
+
end
|
210
356
|
end
|
211
357
|
end
|
212
358
|
|
@@ -253,7 +399,7 @@ describe Ably::Rest::Client do
|
|
253
399
|
end
|
254
400
|
|
255
401
|
it 'fails immediately and raises a Faraday Error' do
|
256
|
-
expect { client.auth.request_token }.to raise_error Ably::Exceptions::
|
402
|
+
expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeout
|
257
403
|
end
|
258
404
|
|
259
405
|
context 'fallback hosts' do
|
@@ -266,11 +412,27 @@ describe Ably::Rest::Client do
|
|
266
412
|
end
|
267
413
|
|
268
414
|
specify 'are never used' do
|
269
|
-
expect { client.auth.request_token }.to raise_error Ably::Exceptions::
|
415
|
+
expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeout
|
270
416
|
expect(custom_host_request_stub).to have_been_requested
|
271
417
|
end
|
272
418
|
end
|
273
419
|
end
|
274
420
|
end
|
421
|
+
|
422
|
+
context '#auth' do
|
423
|
+
let(:dummy_auth_url) { 'http://dummy.url' }
|
424
|
+
let(:unique_ttl) { 1234 }
|
425
|
+
let(:client_options) { default_options.merge(auth_url: dummy_auth_url, ttl: unique_ttl) }
|
426
|
+
|
427
|
+
|
428
|
+
it 'is provides access to the Auth object' do
|
429
|
+
expect(client.auth).to be_kind_of(Ably::Auth)
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'configures the Auth object with all ClientOptions passed to client in the initializer' do
|
433
|
+
expect(client.auth.options[:ttl]).to eql(unique_ttl)
|
434
|
+
expect(client.auth.options[:auth_url]).to eql(dummy_auth_url)
|
435
|
+
end
|
436
|
+
end
|
275
437
|
end
|
276
438
|
end
|