ably-rest 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -43
  3. data/SPEC.md +707 -580
  4. data/lib/submodules/ably-ruby/.travis.yml +1 -0
  5. data/lib/submodules/ably-ruby/CHANGELOG.md +143 -3
  6. data/lib/submodules/ably-ruby/README.md +1 -1
  7. data/lib/submodules/ably-ruby/SPEC.md +842 -520
  8. data/lib/submodules/ably-ruby/ably.gemspec +1 -1
  9. data/lib/submodules/ably-ruby/lib/ably/auth.rb +114 -87
  10. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +40 -14
  11. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +3 -5
  12. data/lib/submodules/ably-ruby/lib/ably/models/paginated_result.rb +3 -12
  13. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +8 -2
  14. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -3
  15. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +1 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +1 -1
  17. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +7 -1
  18. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
  19. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +6 -3
  20. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +2 -2
  21. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +1 -1
  22. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  23. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +191 -0
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +97 -25
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +11 -3
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +22 -6
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +73 -40
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +48 -33
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +17 -3
  31. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +43 -16
  32. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +57 -26
  33. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +3 -1
  34. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -2
  35. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  36. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +242 -0
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +277 -5
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channels_spec.rb +64 -0
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +26 -5
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +23 -6
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +167 -16
  43. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +9 -8
  44. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +1 -0
  45. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +121 -10
  46. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +13 -1
  47. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +161 -79
  48. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +3 -3
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +142 -15
  50. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +23 -0
  51. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +180 -18
  52. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +8 -8
  53. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +136 -25
  54. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +60 -4
  55. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +54 -3
  56. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +7 -6
  57. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +1 -9
  58. data/lib/submodules/ably-ruby/spec/unit/models/paginated_result_spec.rb +1 -18
  59. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  60. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +21 -1
  61. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +10 -3
  62. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +27 -8
  63. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +0 -8
  64. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +7 -7
  65. 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::InvalidRequest.new('Authentication failure', 401, 40140)
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 InvalidToken exception' do
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::InvalidToken
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(key: api_key, environment: environment, protocol: protocol)
11
+ Ably::Rest::Client.new(client_options)
10
12
  end
11
13
 
12
14
  describe '#publish' do
13
- let(:channel) { client.channel('test') }
14
- let(:event) { 'foo' }
15
- let(:message) { 'woop!' }
15
+ let(:channel) { client.channel(random_str) }
16
+ let(:name) { 'foo' }
17
+ let(:data) { 'woop!' }
16
18
 
17
- it 'should publish the message and return true indicating success' do
18
- expect(channel.publish(event, message)).to eql(true)
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 'should return the current message history for the channel' do
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
- it 'should all be after the messages were published' do
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 'should be unique' do
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 'should return paged history using the PaginatedResult model' do
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
- expect(page_3).to_not be_first
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(:default_options) do
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 = default_options.merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
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 a :auth_callback Proc' do
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) { default_options.merge(auth_url: token_request_url, auth_method: :get) }
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
- before do
31
- allow(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, :auth_method => :get).and_return(token_request)
70
+ it 'uses token authentication' do
71
+ expect(client.auth).to be_using_token_auth
32
72
  end
33
73
 
34
- it 'sends an HTTP request to the provided URL to get a new token' do
35
- expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
36
- expect(client.auth.current_token_details.client_id).to eql(client_id)
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
- let(:token_request_options) { { key_name: key_name, key_secret: key_secret, ttl: Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER } }
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::ConnectionTimeoutError
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 do
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 do
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::ConnectionTimeoutError
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::ConnectionTimeoutError
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::ConnectionTimeoutError
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