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.
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