ably 0.8.2 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +185 -0
- data/LICENSE +15 -0
- data/README.md +8 -4
- data/SPEC.md +999 -531
- data/ably.gemspec +1 -1
- data/lib/ably.rb +1 -1
- data/lib/ably/auth.rb +114 -87
- data/lib/ably/exceptions.rb +40 -14
- data/lib/ably/models/message.rb +3 -5
- data/lib/ably/models/paginated_result.rb +3 -12
- data/lib/ably/models/presence_message.rb +8 -2
- data/lib/ably/models/protocol_message.rb +15 -3
- data/lib/ably/models/stat.rb +1 -1
- data/lib/ably/models/token_details.rb +1 -1
- data/lib/ably/modules/channels_collection.rb +7 -1
- data/lib/ably/modules/conversions.rb +1 -1
- data/lib/ably/modules/encodeable.rb +6 -3
- data/lib/ably/modules/message_pack.rb +2 -2
- data/lib/ably/modules/model_common.rb +1 -1
- data/lib/ably/modules/state_machine.rb +2 -2
- data/lib/ably/realtime.rb +1 -0
- data/lib/ably/realtime/auth.rb +191 -0
- data/lib/ably/realtime/channel.rb +97 -25
- data/lib/ably/realtime/channel/channel_manager.rb +11 -3
- data/lib/ably/realtime/client.rb +22 -6
- data/lib/ably/realtime/connection.rb +74 -41
- data/lib/ably/realtime/connection/connection_manager.rb +48 -33
- data/lib/ably/realtime/presence.rb +17 -3
- data/lib/ably/rest/channel.rb +43 -16
- data/lib/ably/rest/client.rb +57 -26
- data/lib/ably/rest/middleware/exceptions.rb +3 -1
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -2
- data/lib/ably/rest/presence.rb +1 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/auth_spec.rb +242 -0
- data/spec/acceptance/realtime/channel_spec.rb +277 -5
- data/spec/acceptance/realtime/channels_spec.rb +64 -0
- data/spec/acceptance/realtime/client_spec.rb +26 -5
- data/spec/acceptance/realtime/connection_failures_spec.rb +23 -6
- data/spec/acceptance/realtime/connection_spec.rb +167 -16
- data/spec/acceptance/realtime/message_spec.rb +9 -8
- data/spec/acceptance/realtime/presence_history_spec.rb +1 -0
- data/spec/acceptance/realtime/presence_spec.rb +121 -10
- data/spec/acceptance/realtime/stats_spec.rb +13 -1
- data/spec/acceptance/rest/auth_spec.rb +161 -79
- data/spec/acceptance/rest/base_spec.rb +3 -3
- data/spec/acceptance/rest/channel_spec.rb +142 -15
- data/spec/acceptance/rest/channels_spec.rb +23 -0
- data/spec/acceptance/rest/client_spec.rb +180 -18
- data/spec/acceptance/rest/message_spec.rb +8 -8
- data/spec/acceptance/rest/presence_spec.rb +136 -25
- data/spec/acceptance/rest/stats_spec.rb +60 -4
- data/spec/shared/client_initializer_behaviour.rb +54 -3
- data/spec/unit/auth_spec.rb +7 -6
- data/spec/unit/models/message_spec.rb +1 -9
- data/spec/unit/models/paginated_result_spec.rb +1 -18
- data/spec/unit/models/presence_message_spec.rb +1 -1
- data/spec/unit/models/protocol_message_spec.rb +21 -1
- data/spec/unit/realtime/channel_spec.rb +10 -3
- data/spec/unit/realtime/channels_spec.rb +27 -8
- data/spec/unit/rest/channel_spec.rb +0 -8
- data/spec/unit/rest/client_spec.rb +7 -7
- metadata +13 -7
- data/LICENSE.txt +0 -22
@@ -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
|