ably 0.1.5 → 0.1.6

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
File without changes
@@ -0,0 +1,277 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe 'Ably::Realtime::Presence Messages' do
5
+ include RSpec::EventMachine
6
+
7
+ [:msgpack, :json].each do |protocol|
8
+ context "over #{protocol}" do
9
+ let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
10
+
11
+ let(:channel_name) { "presence-#{SecureRandom.hex(2)}" }
12
+
13
+ let(:anonymous_client) { Ably::Realtime::Client.new(default_options) }
14
+ let(:client_one) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
15
+ let(:client_two) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
16
+
17
+ let(:channel_anonymous_client) { anonymous_client.channel(channel_name) }
18
+ let(:presence_anonymous_client) { channel_anonymous_client.presence }
19
+ let(:channel_client_one) { client_one.channel(channel_name) }
20
+ let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name) }
21
+ let(:presence_client_one) { channel_client_one.presence }
22
+ let(:channel_client_two) { client_two.channel(channel_name) }
23
+ let(:presence_client_two) { channel_client_two.presence }
24
+
25
+ let(:client_data_payload) { SecureRandom.hex(8) }
26
+
27
+ specify 'an attached channel that is not presence maintains presence state' do
28
+ run_reactor do
29
+ channel_anonymous_client.attach do
30
+ presence_anonymous_client.subscribe(:enter) do |presence_message|
31
+ expect(presence_message.client_id).to eql(client_one.client_id)
32
+ members = presence_anonymous_client.get
33
+ expect(members.first.client_id).to eql(client_one.client_id)
34
+ expect(members.first.action).to eq(:enter)
35
+
36
+ presence_anonymous_client.subscribe(:leave) do |presence_message|
37
+ expect(presence_message.client_id).to eql(client_one.client_id)
38
+ members = presence_anonymous_client.get
39
+ expect(members.count).to eql(0)
40
+
41
+ stop_reactor
42
+ end
43
+ end
44
+ end
45
+
46
+ presence_client_one.enter do
47
+ presence_client_one.leave
48
+ end
49
+ end
50
+ end
51
+
52
+ it '#enter allows client_id to be set on enter for anonymous clients' do
53
+ run_reactor do
54
+ channel_anonymous_client.presence.enter client_id: "123"
55
+
56
+ channel_anonymous_client.presence.subscribe do |presence|
57
+ expect(presence.client_id).to eq("123")
58
+ stop_reactor
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'enters and then leaves' do
64
+ leave_callback_called = false
65
+ run_reactor do
66
+ presence_client_one.enter do
67
+ presence_client_one.leave do |presence|
68
+ leave_callback_called = true
69
+ end
70
+ presence_client_one.on(:left) do
71
+ EventMachine.next_tick do
72
+ expect(leave_callback_called).to eql(true)
73
+ stop_reactor
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ it 'enters the :left state if the channel detaches' do
81
+ detached = false
82
+ run_reactor do
83
+ channel_client_one.presence.on(:left) do
84
+ expect(channel_client_one.presence.state).to eq(:left)
85
+ EventMachine.next_tick do
86
+ expect(detached).to eq(true)
87
+ stop_reactor
88
+ end
89
+ end
90
+ channel_client_one.presence.enter do |presence|
91
+ expect(presence.state).to eq(:entered)
92
+ channel_client_one.detach do
93
+ expect(channel_client_one.state).to eq(:detached)
94
+ detached = true
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ specify '#get returns the current member on the channel' do
101
+ run_reactor do
102
+ presence_client_one.enter do
103
+ members = presence_client_one.get
104
+ expect(members.count).to eq(1)
105
+
106
+ expect(client_one.client_id).to_not be_nil
107
+
108
+ this_member = members.first
109
+ expect(this_member.client_id).to eql(client_one.client_id)
110
+
111
+ stop_reactor
112
+ end
113
+ end
114
+ end
115
+
116
+ specify '#get returns no members on the channel following an enter and leave' do
117
+ run_reactor do
118
+ presence_client_one.enter do
119
+ presence_client_one.leave do
120
+ expect(presence_client_one.get).to eq([])
121
+ stop_reactor
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ specify 'verify two clients appear in members from #get' do
128
+ run_reactor do
129
+ presence_client_one.enter(client_data: client_data_payload)
130
+ presence_client_two.enter
131
+
132
+ entered_callback = Proc.new do
133
+ next unless presence_client_one.state == :entered && presence_client_two.state == :entered
134
+
135
+ EventMachine.add_timer(0.25) do
136
+ expect(presence_client_one.get.count).to eq(presence_client_two.get.count)
137
+
138
+ members = presence_client_one.get
139
+ member_client_one = members.find { |presence| presence.client_id == client_one.client_id }
140
+ member_client_two = members.find { |presence| presence.client_id == client_two.client_id }
141
+
142
+ expect(member_client_one).to be_a(Ably::Models::PresenceMessage)
143
+ expect(member_client_one.client_data).to eql(client_data_payload)
144
+ expect(member_client_two).to be_a(Ably::Models::PresenceMessage)
145
+
146
+ stop_reactor
147
+ end
148
+ end
149
+
150
+ presence_client_one.on :entered, &entered_callback
151
+ presence_client_two.on :entered, &entered_callback
152
+ end
153
+ end
154
+
155
+ specify '#subscribe and #unsubscribe to presence events' do
156
+ run_reactor do
157
+ client_two_subscribe_messages = []
158
+
159
+ subscribe_client_one_leaving_callback = Proc.new do |presence_message|
160
+ expect(presence_message.client_id).to eql(client_one.client_id)
161
+ expect(presence_message.client_data).to eql(client_data_payload)
162
+ expect(presence_message.action).to eq(:leave)
163
+
164
+ stop_reactor
165
+ end
166
+
167
+ subscribe_self_callback = Proc.new do |presence_message|
168
+ if presence_message.client_id == client_two.client_id
169
+ expect(presence_message.action).to eq(:enter)
170
+
171
+ presence_client_two.unsubscribe &subscribe_self_callback
172
+ presence_client_two.subscribe &subscribe_client_one_leaving_callback
173
+
174
+ presence_client_one.leave client_data: client_data_payload
175
+ end
176
+ end
177
+
178
+ presence_client_one.enter do
179
+ presence_client_two.enter
180
+ presence_client_two.subscribe &subscribe_self_callback
181
+ end
182
+ end
183
+ end
184
+
185
+ specify 'verify REST #get returns current members' do
186
+ run_reactor do
187
+ presence_client_one.enter(client_data: client_data_payload) do
188
+ members = channel_rest_client_one.presence.get
189
+ this_member = members.first
190
+
191
+ expect(this_member).to be_a(Ably::Models::PresenceMessage)
192
+ expect(this_member.client_id).to eql(client_one.client_id)
193
+ expect(this_member.client_data).to eql(client_data_payload)
194
+
195
+ stop_reactor
196
+ end
197
+ end
198
+ end
199
+
200
+ specify 'verify REST #get returns no members once left' do
201
+ run_reactor do
202
+ presence_client_one.enter(client_data: client_data_payload) do
203
+ presence_client_one.leave do
204
+ members = channel_rest_client_one.presence.get
205
+ expect(members.count).to eql(0)
206
+ stop_reactor
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ specify 'expect :left event once underlying connection is closed' do
213
+ run_reactor do
214
+ presence_client_one.on(:left) do
215
+ expect(presence_client_one.state).to eq(:left)
216
+ stop_reactor
217
+ end
218
+ presence_client_one.enter do
219
+ client_one.close
220
+ end
221
+ end
222
+ end
223
+
224
+ specify 'expect :left event with no client data to retain original client_data in Leave event' do
225
+ run_reactor do
226
+ presence_client_one.subscribe(:leave) do |message|
227
+ expect(presence_client_one.get.count).to eq(0)
228
+ expect(message.client_data).to eq(client_data_payload)
229
+ stop_reactor
230
+ end
231
+ presence_client_one.enter(client_data: client_data_payload) do
232
+ presence_client_one.leave
233
+ end
234
+ end
235
+ end
236
+
237
+ specify '#update automatically connects' do
238
+ run_reactor do
239
+ presence_client_one.update(client_data: client_data_payload) do
240
+ expect(presence_client_one.state).to eq(:entered)
241
+ stop_reactor
242
+ end
243
+ end
244
+ end
245
+
246
+ specify '#update changes the client_data' do
247
+ run_reactor do
248
+ presence_client_one.enter(client_data: 'prior') do
249
+ presence_client_one.update(client_data: client_data_payload)
250
+ end
251
+ presence_client_one.subscribe(:update) do |message|
252
+ expect(message.client_data).to eql(client_data_payload)
253
+ stop_reactor
254
+ end
255
+ end
256
+ end
257
+
258
+ it 'raises an exception if client_id is not set' do
259
+ run_reactor do
260
+ expect { channel_anonymous_client.presence.enter }.to raise_error(Ably::Exceptions::Standard, /without a client_id/)
261
+ stop_reactor
262
+ end
263
+ end
264
+
265
+ it '#leave raises an exception if not entered' do
266
+ run_reactor do
267
+ expect { channel_anonymous_client.presence.leave }.to raise_error(Ably::Exceptions::Standard, /Unable to leave presence channel that is not entered/)
268
+ stop_reactor
269
+ end
270
+ end
271
+
272
+ skip 'ensure member_id is unique an updated on ENTER'
273
+ skip 'stop a call to get when the channel has not been entered'
274
+ skip 'stop a call to get when the channel has been entered but the list is not up to date'
275
+ end
276
+ end
277
+ end
@@ -2,432 +2,436 @@ require "spec_helper"
2
2
  require "securerandom"
3
3
 
4
4
  describe "REST" do
5
- let(:client) do
6
- Ably::Rest::Client.new(api_key: api_key, environment: environment)
7
- end
8
- let(:auth) { client.auth }
9
-
10
- describe "#request_token" do
11
- let(:ttl) { 30 * 60 }
12
- let(:capability) { { :foo => ["publish"] } }
13
-
14
- it "returns the requested token" do
15
- actual_token = auth.request_token(
16
- ttl: ttl,
17
- capability: capability
18
- )
19
-
20
- expect(actual_token.id).to match(/^#{app_id}\.[\w-]+$/)
21
- expect(actual_token.key_id).to match(/^#{key_id}$/)
22
- expect(actual_token.issued_at).to be_within(2).of(Time.now)
23
- expect(actual_token.expires_at).to be_within(2).of(Time.now + ttl)
24
- end
25
-
26
- %w(client_id ttl timestamp capability nonce).each do |option|
27
- context "option :#{option}", webmock: true do
28
- let(:random) { SecureRandom.random_number(1_000_000_000).to_s }
29
- let(:options) { { option.to_sym => random } }
30
-
31
- let(:token_response) { { access_token: {} }.to_json }
32
- let!(:request_token_stub) do
33
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
34
- with(:body => hash_including({ option => random })).
35
- to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
36
- end
37
-
38
- before { auth.request_token options }
39
-
40
- it 'overrides default' do
41
- expect(request_token_stub).to have_been_requested
42
- end
43
- end
44
- end
45
-
46
- context 'with :key_id & :key_secret options', webmock: true do
47
- let(:key_id) { SecureRandom.hex }
48
- let(:key_secret) { SecureRandom.hex }
49
- let(:nonce) { SecureRandom.hex }
50
- let(:token_options) { { key_id: key_id, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
51
- let(:token_request) { auth.create_token_request(token_options) }
52
- let(:mac) do
53
- hmac_for(token_request, key_secret)
54
- end
55
-
56
- let(:token_response) { { access_token: {} }.to_json }
57
- let!(:request_token_stub) do
58
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
59
- with(:body => hash_including({ 'mac' => mac })).
60
- to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
5
+ [:msgpack, :json].each do |protocol|
6
+ context "over #{protocol}" do
7
+ let(:client) do
8
+ Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
61
9
  end
10
+ let(:auth) { client.auth }
62
11
 
63
- let!(:token) { auth.request_token(token_options) }
12
+ describe "#request_token" do
13
+ let(:ttl) { 30 * 60 }
14
+ let(:capability) { { :foo => ["publish"] } }
64
15
 
65
- specify 'key_id is used in request and signing uses key_secret' do
66
- expect(request_token_stub).to have_been_requested
67
- end
68
- end
16
+ it "returns the requested token" do
17
+ actual_token = auth.request_token(
18
+ ttl: ttl,
19
+ capability: capability
20
+ )
69
21
 
70
- context "with :query_time option" do
71
- let(:options) { { query_time: true } }
22
+ expect(actual_token.id).to match(/^#{app_id}\.[\w-]+$/)
23
+ expect(actual_token.key_id).to match(/^#{key_id}$/)
24
+ expect(actual_token.issued_at).to be_within(2).of(Time.now)
25
+ expect(actual_token.expires_at).to be_within(2).of(Time.now + ttl)
26
+ end
72
27
 
73
- it 'queries the server for the time' do
74
- expect(client).to receive(:time).and_call_original
75
- auth.request_token(options)
76
- end
77
- end
28
+ %w(client_id ttl timestamp capability nonce).each do |option|
29
+ context "option :#{option}", webmock: true do
30
+ let(:random) { SecureRandom.random_number(1_000_000_000).to_s }
31
+ let(:options) { { option.to_sym => random } }
78
32
 
79
- context "without :query_time option" do
80
- let(:options) { { query_time: false } }
33
+ let(:token_response) { { access_token: {} }.to_json }
34
+ let!(:request_token_stub) do
35
+ stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
36
+ with(:body => hash_including({ option => random })).
37
+ to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
38
+ end
81
39
 
82
- it 'queries the server for the time' do
83
- expect(client).to_not receive(:time)
84
- auth.request_token(options)
85
- end
86
- end
40
+ before { auth.request_token options }
87
41
 
88
- context 'with :auth_url option', webmock: true do
89
- let(:auth_url) { 'https://www.fictitious.com/get_token' }
90
- let(:token_request) { { id: key_id }.to_json }
91
- let(:token_response) { { access_token: { } }.to_json }
92
- let(:query_params) { nil }
93
- let(:headers) { nil }
94
- let(:auth_method) { :get }
95
- let(:options) do
96
- {
97
- auth_url: auth_url,
98
- auth_params: query_params,
99
- auth_headers: headers,
100
- auth_method: auth_method
101
- }
102
- end
42
+ it 'overrides default' do
43
+ expect(request_token_stub).to have_been_requested
44
+ end
45
+ end
46
+ end
103
47
 
104
- let!(:auth_url_request_stub) do
105
- stub = stub_request(auth_method, auth_url)
106
- stub.with(:query => hash_including(query_params)) unless query_params.nil?
107
- stub.with(:header => hash_including(headers)) unless headers.nil?
108
- stub.to_return(:status => 201, :body => token_request, :headers => { 'Content-Type' => 'application/json' })
109
- end
48
+ context 'with :key_id & :key_secret options', webmock: true do
49
+ let(:key_id) { SecureRandom.hex }
50
+ let(:key_secret) { SecureRandom.hex }
51
+ let(:nonce) { SecureRandom.hex }
52
+ let(:token_options) { { key_id: key_id, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
53
+ let(:token_request) { auth.create_token_request(token_options) }
54
+ let(:mac) do
55
+ hmac_for(token_request, key_secret)
56
+ end
110
57
 
111
- let!(:request_token_stub) do
112
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
113
- with(:body => hash_including({ 'id' => key_id })).
114
- to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
115
- end
58
+ let(:token_response) { { access_token: {} }.to_json }
59
+ let!(:request_token_stub) do
60
+ stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
61
+ with(:body => hash_including({ 'mac' => mac })).
62
+ to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
63
+ end
116
64
 
117
- context 'valid' do
118
- before { auth.request_token options }
65
+ let!(:token) { auth.request_token(token_options) }
119
66
 
120
- context 'and default options' do
121
- it 'requests a token from :auth_url' do
67
+ specify 'key_id is used in request and signing uses key_secret' do
122
68
  expect(request_token_stub).to have_been_requested
123
- expect(auth_url_request_stub).to have_been_requested
124
69
  end
125
70
  end
126
71
 
127
- context 'with params' do
128
- let(:query_params) { { 'key' => SecureRandom.hex } }
129
- it 'requests a token from :auth_url' do
130
- expect(request_token_stub).to have_been_requested
131
- expect(auth_url_request_stub).to have_been_requested
72
+ context "with :query_time option" do
73
+ let(:options) { { query_time: true } }
74
+
75
+ it 'queries the server for the time' do
76
+ expect(client).to receive(:time).and_call_original
77
+ auth.request_token(options)
132
78
  end
133
79
  end
134
80
 
135
- context 'with headers' do
136
- let(:headers) { { 'key' => SecureRandom.hex } }
137
- it 'requests a token from :auth_url' do
138
- expect(request_token_stub).to have_been_requested
139
- expect(auth_url_request_stub).to have_been_requested
81
+ context "without :query_time option" do
82
+ let(:options) { { query_time: false } }
83
+
84
+ it 'queries the server for the time' do
85
+ expect(client).to_not receive(:time)
86
+ auth.request_token(options)
140
87
  end
141
88
  end
142
89
 
143
- context 'with POST' do
144
- let(:auth_method) { :post }
145
- it 'requests a token from :auth_url' do
146
- expect(request_token_stub).to have_been_requested
147
- expect(auth_url_request_stub).to have_been_requested
90
+ context 'with :auth_url option', webmock: true do
91
+ let(:auth_url) { 'https://www.fictitious.com/get_token' }
92
+ let(:token_request) { { id: key_id }.to_json }
93
+ let(:token_response) { { access_token: { } }.to_json }
94
+ let(:query_params) { nil }
95
+ let(:headers) { nil }
96
+ let(:auth_method) { :get }
97
+ let(:options) do
98
+ {
99
+ auth_url: auth_url,
100
+ auth_params: query_params,
101
+ auth_headers: headers,
102
+ auth_method: auth_method
103
+ }
148
104
  end
149
- end
150
- end
151
105
 
152
- context 'when response is invalid' do
153
- context '500' do
154
106
  let!(:auth_url_request_stub) do
155
- stub_request(auth_method, auth_url).to_return(:status => 500)
107
+ stub = stub_request(auth_method, auth_url)
108
+ stub.with(:query => hash_including(query_params)) unless query_params.nil?
109
+ stub.with(:header => hash_including(headers)) unless headers.nil?
110
+ stub.to_return(:status => 201, :body => token_request, :headers => { 'Content-Type' => 'application/json' })
156
111
  end
157
112
 
158
- it 'raises ServerError' do
159
- expect { auth.request_token options }.to raise_error(Ably::Exceptions::ServerError)
113
+ let!(:request_token_stub) do
114
+ stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
115
+ with(:body => hash_including({ 'id' => key_id })).
116
+ to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
160
117
  end
161
- end
162
118
 
163
- context 'XML' do
164
- let!(:auth_url_request_stub) do
165
- stub_request(auth_method, auth_url).
166
- to_return(:status => 201, :body => '<xml></xml>', :headers => { 'Content-Type' => 'application/xml' })
119
+ context 'valid' do
120
+ before { auth.request_token options }
121
+
122
+ context 'and default options' do
123
+ it 'requests a token from :auth_url' do
124
+ expect(request_token_stub).to have_been_requested
125
+ expect(auth_url_request_stub).to have_been_requested
126
+ end
127
+ end
128
+
129
+ context 'with params' do
130
+ let(:query_params) { { 'key' => SecureRandom.hex } }
131
+ it 'requests a token from :auth_url' do
132
+ expect(request_token_stub).to have_been_requested
133
+ expect(auth_url_request_stub).to have_been_requested
134
+ end
135
+ end
136
+
137
+ context 'with headers' do
138
+ let(:headers) { { 'key' => SecureRandom.hex } }
139
+ it 'requests a token from :auth_url' do
140
+ expect(request_token_stub).to have_been_requested
141
+ expect(auth_url_request_stub).to have_been_requested
142
+ end
143
+ end
144
+
145
+ context 'with POST' do
146
+ let(:auth_method) { :post }
147
+ it 'requests a token from :auth_url' do
148
+ expect(request_token_stub).to have_been_requested
149
+ expect(auth_url_request_stub).to have_been_requested
150
+ end
151
+ end
167
152
  end
168
153
 
169
- it 'raises InvalidResponseBody' do
170
- expect { auth.request_token options }.to raise_error(Ably::Exceptions::InvalidResponseBody)
154
+ context 'when response is invalid' do
155
+ context '500' do
156
+ let!(:auth_url_request_stub) do
157
+ stub_request(auth_method, auth_url).to_return(:status => 500)
158
+ end
159
+
160
+ it 'raises ServerError' do
161
+ expect { auth.request_token options }.to raise_error(Ably::Exceptions::ServerError)
162
+ end
163
+ end
164
+
165
+ context 'XML' do
166
+ let!(:auth_url_request_stub) do
167
+ stub_request(auth_method, auth_url).
168
+ to_return(:status => 201, :body => '<xml></xml>', :headers => { 'Content-Type' => 'application/xml' })
169
+ end
170
+
171
+ it 'raises InvalidResponseBody' do
172
+ expect { auth.request_token options }.to raise_error(Ably::Exceptions::InvalidResponseBody)
173
+ end
174
+ end
171
175
  end
172
176
  end
173
- end
174
- end
175
177
 
176
- context 'with auth_block' do
177
- let(:client_id) { SecureRandom.hex }
178
- let(:options) { { client_id: client_id } }
179
- let!(:token) do
180
- auth.request_token(options) do |block_options|
181
- @block_called = true
182
- @block_options = block_options
183
- auth.create_token_request(client_id: client_id)
184
- end
185
- end
178
+ context 'with auth_block' do
179
+ let(:client_id) { SecureRandom.hex }
180
+ let(:options) { { client_id: client_id } }
181
+ let!(:token) do
182
+ auth.request_token(options) do |block_options|
183
+ @block_called = true
184
+ @block_options = block_options
185
+ auth.create_token_request(client_id: client_id)
186
+ end
187
+ end
186
188
 
187
- it 'calls the block' do
188
- expect(@block_called).to eql(true)
189
- expect(@block_options).to include(options)
190
- end
189
+ it 'calls the block' do
190
+ expect(@block_called).to eql(true)
191
+ expect(@block_options).to include(options)
192
+ end
191
193
 
192
- it 'uses the token request when requesting a new token' do
193
- expect(token.client_id).to eql(client_id)
194
+ it 'uses the token request when requesting a new token' do
195
+ expect(token.client_id).to eql(client_id)
196
+ end
197
+ end
194
198
  end
195
- end
196
- end
197
199
 
198
- describe '#authorise' do
199
- context 'with no previous authorisation' do
200
- let(:request_options) do
201
- { auth_url: 'http://somewhere.com/' }
202
- end
200
+ describe '#authorise' do
201
+ context 'with no previous authorisation' do
202
+ let(:request_options) do
203
+ { auth_url: 'http://somewhere.com/' }
204
+ end
203
205
 
204
- it 'has no current_token' do
205
- expect(auth.current_token).to be_nil
206
- end
206
+ it 'has no current_token' do
207
+ expect(auth.current_token).to be_nil
208
+ end
207
209
 
208
- it 'passes all options to request_token' do
209
- expect(auth).to receive(:request_token).with(request_options)
210
- auth.authorise request_options
211
- end
210
+ it 'passes all options to request_token' do
211
+ expect(auth).to receive(:request_token).with(request_options)
212
+ auth.authorise request_options
213
+ end
212
214
 
213
- it 'returns a valid token' do
214
- expect(auth.authorise).to be_a(Ably::Token)
215
- end
215
+ it 'returns a valid token' do
216
+ expect(auth.authorise).to be_a(Ably::Models::Token)
217
+ end
216
218
 
217
- it 'issues a new token if option :force => true' do
218
- expect { auth.authorise(force: true) }.to change { auth.current_token }
219
- end
220
- end
219
+ it 'issues a new token if option :force => true' do
220
+ expect { auth.authorise(force: true) }.to change { auth.current_token }
221
+ end
222
+ end
221
223
 
222
- context 'with previous authorisation' do
223
- before do
224
- auth.authorise
225
- expect(auth.current_token).to_not be_expired
226
- end
224
+ context 'with previous authorisation' do
225
+ before do
226
+ auth.authorise
227
+ expect(auth.current_token).to_not be_expired
228
+ end
227
229
 
228
- it 'does not request a token if token is not expired' do
229
- expect(auth).to_not receive(:request_token)
230
- auth.authorise
231
- end
230
+ it 'does not request a token if token is not expired' do
231
+ expect(auth).to_not receive(:request_token)
232
+ auth.authorise
233
+ end
232
234
 
233
- it 'requests a new token if token is expired' do
234
- allow(auth.current_token).to receive(:expired?).and_return(true)
235
- expect(auth).to receive(:request_token)
236
- expect { auth.authorise }.to change { auth.current_token }
237
- end
235
+ it 'requests a new token if token is expired' do
236
+ allow(auth.current_token).to receive(:expired?).and_return(true)
237
+ expect(auth).to receive(:request_token)
238
+ expect { auth.authorise }.to change { auth.current_token }
239
+ end
238
240
 
239
- it 'issues a new token if option :force => true' do
240
- expect { auth.authorise(force: true) }.to change { auth.current_token }
241
+ it 'issues a new token if option :force => true' do
242
+ expect { auth.authorise(force: true) }.to change { auth.current_token }
243
+ end
244
+ end
241
245
  end
242
- end
243
- end
244
246
 
245
- describe "#create_token_request" do
246
- let(:ttl) { 60 * 60 }
247
- let(:capability) { { :foo => ["publish"] } }
248
- let(:options) { Hash.new }
249
- subject { auth.create_token_request(options) }
250
-
251
- it "uses the key ID from the client" do
252
- expect(subject[:id]).to eql(key_id)
253
- end
247
+ describe "#create_token_request" do
248
+ let(:ttl) { 60 * 60 }
249
+ let(:capability) { { :foo => ["publish"] } }
250
+ let(:options) { Hash.new }
251
+ subject { auth.create_token_request(options) }
254
252
 
255
- it "uses the default TTL" do
256
- expect(subject[:ttl]).to eql(Ably::Token::DEFAULTS[:ttl])
257
- end
253
+ it "uses the key ID from the client" do
254
+ expect(subject[:id]).to eql(key_id)
255
+ end
258
256
 
259
- it "uses the default capability" do
260
- expect(subject[:capability]).to eql(Ably::Token::DEFAULTS[:capability].to_json)
261
- end
257
+ it "uses the default TTL" do
258
+ expect(subject[:ttl]).to eql(Ably::Models::Token::DEFAULTS[:ttl])
259
+ end
262
260
 
263
- it "has a unique nonce" do
264
- unique_nonces = 100.times.map { auth.create_token_request[:nonce] }
265
- expect(unique_nonces.uniq.length).to eql(100)
266
- end
261
+ it "uses the default capability" do
262
+ expect(subject[:capability]).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
263
+ end
267
264
 
268
- it "has a nonce of at least 16 characters" do
269
- expect(subject[:nonce].length).to be >= 16
270
- end
265
+ it "has a unique nonce" do
266
+ unique_nonces = 100.times.map { auth.create_token_request[:nonce] }
267
+ expect(unique_nonces.uniq.length).to eql(100)
268
+ end
271
269
 
272
- %w(ttl capability nonce timestamp client_id).each do |attribute|
273
- context "with option :#{attribute}" do
274
- let(:option_value) { SecureRandom.random_number(1_000_000_000) }
275
- before do
276
- options[attribute.to_sym] = option_value
270
+ it "has a nonce of at least 16 characters" do
271
+ expect(subject[:nonce].length).to be >= 16
277
272
  end
278
- it "overrides default" do
279
- expect(subject[attribute.to_sym]).to eql(option_value)
273
+
274
+ %w(ttl capability nonce timestamp client_id).each do |attribute|
275
+ context "with option :#{attribute}" do
276
+ let(:option_value) { SecureRandom.random_number(1_000_000_000) }
277
+ before do
278
+ options[attribute.to_sym] = option_value
279
+ end
280
+ it "overrides default" do
281
+ expect(subject[attribute.to_sym]).to eql(option_value)
282
+ end
283
+ end
280
284
  end
281
- end
282
- end
283
285
 
284
- context "invalid attributes" do
285
- let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
286
- specify 'are ignored' do
287
- expect(subject.keys).to_not include(:is_not_used_by_token_request)
288
- expect(subject.keys).to include(:nonce)
289
- expect(subject[:nonce]).to eql('valid')
290
- end
291
- end
286
+ context "invalid attributes" do
287
+ let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
288
+ specify 'are ignored' do
289
+ expect(subject.keys).to_not include(:is_not_used_by_token_request)
290
+ expect(subject.keys).to include(:nonce)
291
+ expect(subject[:nonce]).to eql('valid')
292
+ end
293
+ end
292
294
 
293
- context "missing key ID and/or secret" do
294
- let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com') }
295
+ context "missing key ID and/or secret" do
296
+ let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
295
297
 
296
- it "should raise an exception if key secret is missing" do
297
- expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
298
- end
298
+ it "should raise an exception if key secret is missing" do
299
+ expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
300
+ end
299
301
 
300
- it "should raise an exception if key id is missing" do
301
- expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
302
- end
303
- end
302
+ it "should raise an exception if key id is missing" do
303
+ expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
304
+ end
305
+ end
304
306
 
305
- context "with :query_time option" do
306
- let(:time) { Time.now - 30 }
307
- let(:options) { { query_time: true } }
307
+ context "with :query_time option" do
308
+ let(:time) { Time.now - 30 }
309
+ let(:options) { { query_time: true } }
308
310
 
309
- it 'queries the server for the time' do
310
- expect(client).to receive(:time).and_return(time)
311
- expect(subject[:timestamp]).to eql(time.to_i)
312
- end
313
- end
311
+ it 'queries the server for the time' do
312
+ expect(client).to receive(:time).and_return(time)
313
+ expect(subject[:timestamp]).to eql(time.to_i)
314
+ end
315
+ end
314
316
 
315
- context "with :timestamp option" do
316
- let(:token_request_time) { Time.now + 5 }
317
- let(:options) { { timestamp: token_request_time } }
317
+ context "with :timestamp option" do
318
+ let(:token_request_time) { Time.now + 5 }
319
+ let(:options) { { timestamp: token_request_time } }
318
320
 
319
- it 'uses the provided timestamp' do
320
- expect(subject[:timestamp]).to eql(token_request_time.to_i)
321
- end
322
- end
321
+ it 'uses the provided timestamp' do
322
+ expect(subject[:timestamp]).to eql(token_request_time.to_i)
323
+ end
324
+ end
323
325
 
324
- context "signing" do
325
- let(:options) do
326
- {
327
- id: SecureRandom.hex,
328
- ttl: SecureRandom.hex,
329
- capability: SecureRandom.hex,
330
- client_id: SecureRandom.hex,
331
- timestamp: SecureRandom.random_number(1_000_000_000),
332
- nonce: SecureRandom.hex
333
- }
334
- end
326
+ context "signing" do
327
+ let(:options) do
328
+ {
329
+ id: SecureRandom.hex,
330
+ ttl: SecureRandom.hex,
331
+ capability: SecureRandom.hex,
332
+ client_id: SecureRandom.hex,
333
+ timestamp: SecureRandom.random_number(1_000_000_000),
334
+ nonce: SecureRandom.hex
335
+ }
336
+ end
335
337
 
336
- it 'generates a valid HMAC' do
337
- hmac = hmac_for(options, key_secret)
338
- expect(subject[:mac]).to eql(hmac)
338
+ it 'generates a valid HMAC' do
339
+ hmac = hmac_for(options, key_secret)
340
+ expect(subject[:mac]).to eql(hmac)
341
+ end
342
+ end
339
343
  end
340
- end
341
- end
342
344
 
343
- context "client with token authentication" do
344
- let(:capability) { { :foo => ["publish"] } }
345
+ context "client with token authentication" do
346
+ let(:capability) { { :foo => ["publish"] } }
345
347
 
346
- describe "with token_id argument" do
347
- let(:ttl) { 60 * 60 }
348
- let(:token) do
349
- auth.request_token(
350
- ttl: ttl,
351
- capability: capability
352
- )
353
- end
354
- let(:token_id) { token.id }
355
- let(:token_auth_client) do
356
- Ably::Rest::Client.new(token_id: token_id, environment: environment)
357
- end
348
+ describe "with token_id argument" do
349
+ let(:ttl) { 60 * 60 }
350
+ let(:token) do
351
+ auth.request_token(
352
+ ttl: ttl,
353
+ capability: capability
354
+ )
355
+ end
356
+ let(:token_id) { token.id }
357
+ let(:token_auth_client) do
358
+ Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
359
+ end
358
360
 
359
- it "authenticates successfully" do
360
- expect(token_auth_client.channel("foo").publish("event", "data")).to be_truthy
361
- end
361
+ it "authenticates successfully" do
362
+ expect(token_auth_client.channel("foo").publish("event", "data")).to be_truthy
363
+ end
362
364
 
363
- it "disallows publishing on unspecified capability channels" do
364
- expect { token_auth_client.channel("bar").publish("event", "data") }.to raise_error do |error|
365
- expect(error).to be_a(Ably::Exceptions::InvalidRequest)
366
- expect(error.status).to eql(401)
367
- expect(error.code).to eql(40160)
368
- end
369
- end
365
+ it "disallows publishing on unspecified capability channels" do
366
+ expect { token_auth_client.channel("bar").publish("event", "data") }.to raise_error do |error|
367
+ expect(error).to be_a(Ably::Exceptions::InvalidToken)
368
+ expect(error.status).to eql(401)
369
+ expect(error.code).to eql(40160)
370
+ end
371
+ end
370
372
 
371
- it "fails if timestamp is invalid" do
372
- expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
373
- expect(error).to be_a(Ably::Exceptions::InvalidRequest)
374
- expect(error.status).to eql(401)
375
- expect(error.code).to eql(40101)
373
+ it "fails if timestamp is invalid" do
374
+ expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
375
+ expect(error).to be_a(Ably::Exceptions::InvalidToken)
376
+ expect(error.status).to eql(401)
377
+ expect(error.code).to eql(40101)
378
+ end
379
+ end
376
380
  end
377
- end
378
- end
379
-
380
- describe "implicit through client id" do
381
- let(:client_id) { '999' }
382
- let(:client) do
383
- Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment)
384
- end
385
- let(:token_id) { 'unique-token-id' }
386
- let(:token_response) do
387
- {
388
- access_token: {
389
- id: token_id
390
- }
391
- }.to_json
392
- end
393
381
 
394
- context 'stubbed', webmock: true do
395
- let!(:request_token_stub) do
396
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
397
- to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
398
- end
399
- let!(:publish_message_stub) do
400
- stub_request(:post, "#{client.endpoint}/channels/foo/publish").
401
- with(headers: { 'Authorization' => "Bearer #{encode64(token_id)}" }).
402
- to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
403
- end
382
+ describe "implicit through client id" do
383
+ let(:client_id) { '999' }
384
+ let(:client) do
385
+ Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment, protocol: protocol)
386
+ end
387
+ let(:token_id) { 'unique-token-id' }
388
+ let(:token_response) do
389
+ {
390
+ access_token: {
391
+ id: token_id
392
+ }
393
+ }.to_json
394
+ end
404
395
 
405
- it "will create a token request" do
406
- client.channel("foo").publish("event", "data")
407
- expect(request_token_stub).to have_been_requested
408
- end
409
- end
396
+ context 'stubbed', webmock: true do
397
+ let!(:request_token_stub) do
398
+ stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
399
+ to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
400
+ end
401
+ let!(:publish_message_stub) do
402
+ stub_request(:post, "#{client.endpoint}/channels/foo/publish").
403
+ with(headers: { 'Authorization' => "Bearer #{encode64(token_id)}" }).
404
+ to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
405
+ end
406
+
407
+ it "will create a token request" do
408
+ client.channel("foo").publish("event", "data")
409
+ expect(request_token_stub).to have_been_requested
410
+ end
411
+ end
410
412
 
411
- context "will create a token" do
412
- let(:token) { client.auth.current_token }
413
+ context "will create a token" do
414
+ let(:token) { client.auth.current_token }
413
415
 
414
- it "before a request is made" do
415
- expect(token).to be_nil
416
- end
416
+ it "before a request is made" do
417
+ expect(token).to be_nil
418
+ end
417
419
 
418
- it "when a message is published" do
419
- expect(client.channel("foo").publish("event", "data")).to be_truthy
420
- end
420
+ it "when a message is published" do
421
+ expect(client.channel("foo").publish("event", "data")).to be_truthy
422
+ end
421
423
 
422
- it "with capability and TTL defaults" do
423
- client.channel("foo").publish("event", "data")
424
+ it "with capability and TTL defaults" do
425
+ client.channel("foo").publish("event", "data")
424
426
 
425
- expect(token).to be_a(Ably::Token)
426
- capability_with_str_key = Ably::Token::DEFAULTS[:capability]
427
- capability = Hash[capability_with_str_key.keys.map(&:to_sym).zip(capability_with_str_key.values)]
428
- expect(token.capability).to eq(capability)
429
- expect(token.expires_at.to_i).to be_within(2).of(Time.now.to_i + Ably::Token::DEFAULTS[:ttl])
430
- expect(token.client_id).to eq(client_id)
427
+ expect(token).to be_a(Ably::Models::Token)
428
+ capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]
429
+ capability = Hash[capability_with_str_key.keys.map(&:to_sym).zip(capability_with_str_key.values)]
430
+ expect(token.capability).to eq(capability)
431
+ expect(token.expires_at.to_i).to be_within(2).of(Time.now.to_i + Ably::Models::Token::DEFAULTS[:ttl])
432
+ expect(token.client_id).to eq(client_id)
433
+ end
434
+ end
431
435
  end
432
436
  end
433
437
  end