ably 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +8 -1
  6. data/Rakefile +10 -0
  7. data/ably.gemspec +18 -18
  8. data/lib/ably.rb +6 -5
  9. data/lib/ably/auth.rb +11 -14
  10. data/lib/ably/exceptions.rb +18 -15
  11. data/lib/ably/logger.rb +102 -0
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/message.rb +19 -5
  14. data/lib/ably/models/message_encoders/base.rb +107 -0
  15. data/lib/ably/models/message_encoders/base64.rb +39 -0
  16. data/lib/ably/models/message_encoders/cipher.rb +80 -0
  17. data/lib/ably/models/message_encoders/json.rb +33 -0
  18. data/lib/ably/models/message_encoders/utf8.rb +33 -0
  19. data/lib/ably/models/paginated_resource.rb +23 -6
  20. data/lib/ably/models/presence_message.rb +19 -7
  21. data/lib/ably/models/protocol_message.rb +5 -4
  22. data/lib/ably/models/token.rb +2 -2
  23. data/lib/ably/modules/channels_collection.rb +0 -3
  24. data/lib/ably/modules/conversions.rb +3 -3
  25. data/lib/ably/modules/encodeable.rb +68 -0
  26. data/lib/ably/modules/event_emitter.rb +10 -4
  27. data/lib/ably/modules/event_machine_helpers.rb +6 -4
  28. data/lib/ably/modules/http_helpers.rb +7 -2
  29. data/lib/ably/modules/model_common.rb +2 -0
  30. data/lib/ably/modules/state_emitter.rb +10 -1
  31. data/lib/ably/realtime.rb +19 -12
  32. data/lib/ably/realtime/channel.rb +26 -13
  33. data/lib/ably/realtime/client.rb +31 -7
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
  36. data/lib/ably/realtime/connection.rb +152 -46
  37. data/lib/ably/realtime/connection/connection_manager.rb +168 -0
  38. data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
  39. data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
  40. data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
  41. data/lib/ably/realtime/presence.rb +38 -13
  42. data/lib/ably/rest.rb +7 -5
  43. data/lib/ably/rest/channel.rb +24 -3
  44. data/lib/ably/rest/client.rb +56 -17
  45. data/lib/ably/rest/middleware/encoder.rb +49 -0
  46. data/lib/ably/rest/middleware/exceptions.rb +3 -2
  47. data/lib/ably/rest/middleware/logger.rb +37 -0
  48. data/lib/ably/rest/presence.rb +10 -2
  49. data/lib/ably/util/crypto.rb +57 -29
  50. data/lib/ably/util/pub_sub.rb +11 -0
  51. data/lib/ably/version.rb +1 -1
  52. data/spec/acceptance/realtime/channel_spec.rb +65 -7
  53. data/spec/acceptance/realtime/connection_spec.rb +123 -27
  54. data/spec/acceptance/realtime/message_spec.rb +319 -34
  55. data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
  56. data/spec/acceptance/realtime/presence_spec.rb +160 -18
  57. data/spec/acceptance/rest/auth_spec.rb +93 -49
  58. data/spec/acceptance/rest/base_spec.rb +10 -10
  59. data/spec/acceptance/rest/channel_spec.rb +35 -19
  60. data/spec/acceptance/rest/channels_spec.rb +8 -8
  61. data/spec/acceptance/rest/message_spec.rb +224 -0
  62. data/spec/acceptance/rest/presence_spec.rb +159 -23
  63. data/spec/acceptance/rest/stats_spec.rb +5 -5
  64. data/spec/acceptance/rest/time_spec.rb +4 -4
  65. data/spec/integration/rest/auth.rb +1 -1
  66. data/spec/resources/crypto-data-128.json +56 -0
  67. data/spec/resources/crypto-data-256.json +56 -0
  68. data/spec/rspec_config.rb +39 -0
  69. data/spec/spec_helper.rb +4 -42
  70. data/spec/support/api_helper.rb +1 -1
  71. data/spec/support/event_machine_helper.rb +0 -5
  72. data/spec/support/protocol_msgbus_helper.rb +3 -3
  73. data/spec/support/test_app.rb +3 -3
  74. data/spec/unit/logger_spec.rb +135 -0
  75. data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  76. data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  77. data/spec/unit/models/message_encoders/json_spec.rb +135 -0
  78. data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
  79. data/spec/unit/models/message_spec.rb +16 -1
  80. data/spec/unit/models/paginated_resource_spec.rb +46 -0
  81. data/spec/unit/models/presence_message_spec.rb +18 -5
  82. data/spec/unit/models/token_spec.rb +1 -1
  83. data/spec/unit/modules/event_emitter_spec.rb +24 -10
  84. data/spec/unit/realtime/channel_spec.rb +3 -3
  85. data/spec/unit/realtime/channels_spec.rb +1 -1
  86. data/spec/unit/realtime/client_spec.rb +44 -2
  87. data/spec/unit/realtime/connection_spec.rb +2 -2
  88. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
  89. data/spec/unit/realtime/presence_spec.rb +1 -1
  90. data/spec/unit/realtime/realtime_spec.rb +3 -3
  91. data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
  92. data/spec/unit/rest/channels_spec.rb +1 -1
  93. data/spec/unit/rest/client_spec.rb +45 -10
  94. data/spec/unit/util/crypto_spec.rb +82 -0
  95. data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
  96. metadata +43 -12
  97. data/spec/acceptance/crypto.rb +0 -63
@@ -0,0 +1,58 @@
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) { "persisted:#{SecureRandom.hex(2)}" }
12
+
13
+ let(:client_one) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
14
+ let(:channel_client_one) { client_one.channel(channel_name) }
15
+ let(:presence_client_one) { channel_client_one.presence }
16
+
17
+ let(:client_two) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
18
+ let(:channel_client_two) { client_two.channel(channel_name) }
19
+ let(:presence_client_two) { channel_client_two.presence }
20
+
21
+ let(:data) { SecureRandom.hex(8) }
22
+
23
+ it 'provides up to the moment presence history' do
24
+ run_reactor do
25
+ presence_client_one.enter(data: data) do
26
+ presence_client_one.leave do
27
+ history = presence_client_one.history
28
+ expect(history.count).to eql(2)
29
+
30
+ expect(history[1].action).to eq(:enter)
31
+ expect(history[1].client_id).to eq(client_one.client_id)
32
+
33
+ expect(history[0].action).to eq(:leave)
34
+ expect(history[0].client_id).to eq(client_one.client_id)
35
+ expect(history[0].data).to eql(data)
36
+
37
+ stop_reactor
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ it 'ensures REST presence history message IDs match ProtocolMessage wrapped message IDs via Realtime' do
44
+ run_reactor do
45
+ presence_client_one.subscribe(:enter) do |message|
46
+ history = presence_client_one.history
47
+ expect(history.count).to eql(1)
48
+
49
+ expect(history[0].id).to eql(message.id)
50
+ stop_reactor
51
+ end
52
+
53
+ presence_client_one.enter(data: data)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -22,7 +22,7 @@ describe 'Ably::Realtime::Presence Messages' do
22
22
  let(:channel_client_two) { client_two.channel(channel_name) }
23
23
  let(:presence_client_two) { channel_client_two.presence }
24
24
 
25
- let(:client_data_payload) { SecureRandom.hex(8) }
25
+ let(:data_payload) { SecureRandom.hex(8) }
26
26
 
27
27
  specify 'an attached channel that is not presence maintains presence state' do
28
28
  run_reactor do
@@ -126,7 +126,7 @@ describe 'Ably::Realtime::Presence Messages' do
126
126
 
127
127
  specify 'verify two clients appear in members from #get' do
128
128
  run_reactor do
129
- presence_client_one.enter(client_data: client_data_payload)
129
+ presence_client_one.enter(data: data_payload)
130
130
  presence_client_two.enter
131
131
 
132
132
  entered_callback = Proc.new do
@@ -140,7 +140,7 @@ describe 'Ably::Realtime::Presence Messages' do
140
140
  member_client_two = members.find { |presence| presence.client_id == client_two.client_id }
141
141
 
142
142
  expect(member_client_one).to be_a(Ably::Models::PresenceMessage)
143
- expect(member_client_one.client_data).to eql(client_data_payload)
143
+ expect(member_client_one.data).to eql(data_payload)
144
144
  expect(member_client_two).to be_a(Ably::Models::PresenceMessage)
145
145
 
146
146
  stop_reactor
@@ -158,7 +158,7 @@ describe 'Ably::Realtime::Presence Messages' do
158
158
 
159
159
  subscribe_client_one_leaving_callback = Proc.new do |presence_message|
160
160
  expect(presence_message.client_id).to eql(client_one.client_id)
161
- expect(presence_message.client_data).to eql(client_data_payload)
161
+ expect(presence_message.data).to eql(data_payload)
162
162
  expect(presence_message.action).to eq(:leave)
163
163
 
164
164
  stop_reactor
@@ -171,7 +171,7 @@ describe 'Ably::Realtime::Presence Messages' do
171
171
  presence_client_two.unsubscribe &subscribe_self_callback
172
172
  presence_client_two.subscribe &subscribe_client_one_leaving_callback
173
173
 
174
- presence_client_one.leave client_data: client_data_payload
174
+ presence_client_one.leave data: data_payload
175
175
  end
176
176
  end
177
177
 
@@ -182,24 +182,24 @@ describe 'Ably::Realtime::Presence Messages' do
182
182
  end
183
183
  end
184
184
 
185
- specify 'verify REST #get returns current members' do
185
+ specify 'REST #get returns current members' do
186
186
  run_reactor do
187
- presence_client_one.enter(client_data: client_data_payload) do
187
+ presence_client_one.enter(data: data_payload) do
188
188
  members = channel_rest_client_one.presence.get
189
189
  this_member = members.first
190
190
 
191
191
  expect(this_member).to be_a(Ably::Models::PresenceMessage)
192
192
  expect(this_member.client_id).to eql(client_one.client_id)
193
- expect(this_member.client_data).to eql(client_data_payload)
193
+ expect(this_member.data).to eql(data_payload)
194
194
 
195
195
  stop_reactor
196
196
  end
197
197
  end
198
198
  end
199
199
 
200
- specify 'verify REST #get returns no members once left' do
200
+ specify 'REST #get returns no members once left' do
201
201
  run_reactor do
202
- presence_client_one.enter(client_data: client_data_payload) do
202
+ presence_client_one.enter(data: data_payload) do
203
203
  presence_client_one.leave do
204
204
  members = channel_rest_client_one.presence.get
205
205
  expect(members.count).to eql(0)
@@ -209,6 +209,148 @@ describe 'Ably::Realtime::Presence Messages' do
209
209
  end
210
210
  end
211
211
 
212
+ context 'encoding and decoding of presence message data' do
213
+ let(:secret_key) { SecureRandom.hex(32) }
214
+ let(:cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 256 } }
215
+ let(:channel_name) { SecureRandom.hex(32) }
216
+ let(:encrypted_channel) { client_one.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
217
+ let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
218
+
219
+ let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
220
+
221
+ let(:data) { { 'key' => SecureRandom.hex(64) } }
222
+ let(:data_as_json) { data.to_json }
223
+ let(:data_as_cipher) { crypto.encrypt(data.to_json) }
224
+
225
+ it 'encrypts presence message data' do
226
+ run_reactor do
227
+ encrypted_channel.attach do
228
+ encrypted_channel.presence.enter data: data
229
+ end
230
+
231
+ encrypted_channel.presence.__incoming_msgbus__.unsubscribe(:presence) # remove all subscribe callbacks that could decrypt the message
232
+ encrypted_channel.presence.__incoming_msgbus__.subscribe(:presence) do |presence|
233
+ if protocol == :json
234
+ expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc/base64')
235
+ expect(crypto.decrypt(Base64.decode64(presence['data']))).to eql(data_as_json)
236
+ else
237
+ expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc')
238
+ expect(crypto.decrypt(presence['data'])).to eql(data_as_json)
239
+ end
240
+ stop_reactor
241
+ end
242
+ end
243
+ end
244
+
245
+ it '#subscribe emits decrypted enter events' do
246
+ run_reactor do
247
+ encrypted_channel.attach do
248
+ encrypted_channel.presence.enter data: data
249
+ end
250
+
251
+ encrypted_channel.presence.subscribe(:enter) do |presence_message|
252
+ expect(presence_message.encoding).to be_nil
253
+ expect(presence_message.data).to eql(data)
254
+ stop_reactor
255
+ end
256
+ end
257
+ end
258
+
259
+ it '#subscribe emits decrypted update events' do
260
+ run_reactor do
261
+ encrypted_channel.attach do
262
+ encrypted_channel.presence.enter(data: 'to be updated') do
263
+ encrypted_channel.presence.update data: data
264
+ end
265
+ end
266
+
267
+ encrypted_channel.presence.subscribe(:update) do |presence_message|
268
+ expect(presence_message.encoding).to be_nil
269
+ expect(presence_message.data).to eql(data)
270
+ stop_reactor
271
+ end
272
+ end
273
+ end
274
+
275
+ it '#subscribe emits decrypted leave events' do
276
+ run_reactor do
277
+ encrypted_channel.attach do
278
+ encrypted_channel.presence.enter(data: 'to be updated') do
279
+ encrypted_channel.presence.leave data: data
280
+ end
281
+ end
282
+
283
+ encrypted_channel.presence.subscribe(:leave) do |presence_message|
284
+ expect(presence_message.encoding).to be_nil
285
+ expect(presence_message.data).to eql(data)
286
+ stop_reactor
287
+ end
288
+ end
289
+ end
290
+
291
+ it '#get returns a list of members with decrypted data' do
292
+ run_reactor do
293
+ encrypted_channel.attach do
294
+ encrypted_channel.presence.enter(data: data) do
295
+ member = encrypted_channel.presence.get.first
296
+ expect(member.encoding).to be_nil
297
+ expect(member.data).to eql(data)
298
+ stop_reactor
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ it 'REST #get returns a list of members with decrypted data' do
305
+ run_reactor do
306
+ encrypted_channel.attach do
307
+ encrypted_channel.presence.enter(data: data) do
308
+ member = channel_rest_client_one.presence.get.first
309
+ expect(member.encoding).to be_nil
310
+ expect(member.data).to eql(data)
311
+ stop_reactor
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ context 'when cipher settings do not match publisher' do
318
+ let(:incompatible_cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 128 } }
319
+ let(:incompatible_encrypted_channel) { client_two.channel(channel_name, encrypted: true, cipher_params: incompatible_cipher_options) }
320
+
321
+ it 'delivers an unencoded presence message left with encoding value' do
322
+ run_reactor do
323
+ incompatible_encrypted_channel.attach do
324
+ encrypted_channel.attach do
325
+ encrypted_channel.presence.enter(data: data) do
326
+ member = incompatible_encrypted_channel.presence.get.first
327
+ expect(member.encoding).to match(/cipher\+aes-256-cbc/)
328
+ expect(member.data).to_not eql(data)
329
+ stop_reactor
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ it 'emits an error when cipher does not match and presence data cannot be decoded' do
337
+ run_reactor do
338
+ incompatible_encrypted_channel.attach do
339
+ incompatible_encrypted_channel.on(:error) do |error|
340
+ expect(error).to be_a(Ably::Exceptions::CipherError)
341
+ expect(error.message).to match(/Cipher algorithm AES-128-CBC does not match/)
342
+ stop_reactor
343
+ end
344
+
345
+ encrypted_channel.attach do
346
+ encrypted_channel.presence.enter data: data
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
353
+
212
354
  specify 'expect :left event once underlying connection is closed' do
213
355
  run_reactor do
214
356
  presence_client_one.on(:left) do
@@ -221,14 +363,14 @@ describe 'Ably::Realtime::Presence Messages' do
221
363
  end
222
364
  end
223
365
 
224
- specify 'expect :left event with no client data to retain original client_data in Leave event' do
366
+ specify 'expect :left event with no client data to retain original data in Leave event' do
225
367
  run_reactor do
226
368
  presence_client_one.subscribe(:leave) do |message|
227
369
  expect(presence_client_one.get.count).to eq(0)
228
- expect(message.client_data).to eq(client_data_payload)
370
+ expect(message.data).to eq(data_payload)
229
371
  stop_reactor
230
372
  end
231
- presence_client_one.enter(client_data: client_data_payload) do
373
+ presence_client_one.enter(data: data_payload) do
232
374
  presence_client_one.leave
233
375
  end
234
376
  end
@@ -236,20 +378,20 @@ describe 'Ably::Realtime::Presence Messages' do
236
378
 
237
379
  specify '#update automatically connects' do
238
380
  run_reactor do
239
- presence_client_one.update(client_data: client_data_payload) do
381
+ presence_client_one.update(data: data_payload) do
240
382
  expect(presence_client_one.state).to eq(:entered)
241
383
  stop_reactor
242
384
  end
243
385
  end
244
386
  end
245
387
 
246
- specify '#update changes the client_data' do
388
+ specify '#update changes the data' do
247
389
  run_reactor do
248
- presence_client_one.enter(client_data: 'prior') do
249
- presence_client_one.update(client_data: client_data_payload)
390
+ presence_client_one.enter(data: 'prior') do
391
+ presence_client_one.update(data: data_payload)
250
392
  end
251
393
  presence_client_one.subscribe(:update) do |message|
252
- expect(message.client_data).to eql(client_data_payload)
394
+ expect(message.data).to eql(data_payload)
253
395
  stop_reactor
254
396
  end
255
397
  end
@@ -1,19 +1,45 @@
1
- require "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe Ably::Auth do
5
+ include Ably::Modules::Conversions
3
6
 
4
- describe "REST" do
5
7
  [:msgpack, :json].each do |protocol|
6
8
  context "over #{protocol}" do
7
9
  let(:client) do
8
10
  Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
9
11
  end
10
12
  let(:auth) { client.auth }
13
+ let(:content_type) do
14
+ if protocol == :msgpack
15
+ 'application/x-msgpack'
16
+ else
17
+ 'application/json'
18
+ end
19
+ end
20
+
21
+ def request_body_includes(request, protocol, key, val)
22
+ body = if protocol == :msgpack
23
+ MessagePack.unpack(request.body)
24
+ else
25
+ JSON.parse(request.body)
26
+ end
27
+ body[key.to_s].to_s == val.to_s
28
+ end
29
+
30
+ def serialize(object, protocol)
31
+ if protocol == :msgpack
32
+ MessagePack.pack(token_response)
33
+ else
34
+ JSON.dump(token_response)
35
+ end
36
+ end
11
37
 
12
38
  describe "#request_token" do
13
39
  let(:ttl) { 30 * 60 }
14
- let(:capability) { { :foo => ["publish"] } }
40
+ let(:capability) { { :foo => ['publish'] } }
15
41
 
16
- it "returns the requested token" do
42
+ it 'returns the requested token' do
17
43
  actual_token = auth.request_token(
18
44
  ttl: ttl,
19
45
  capability: capability
@@ -25,16 +51,21 @@ describe "REST" do
25
51
  expect(actual_token.expires_at).to be_within(2).of(Time.now + ttl)
26
52
  end
27
53
 
28
- %w(client_id ttl timestamp capability nonce).each do |option|
54
+ %w(client_id capability nonce timestamp ttl).each do |option|
29
55
  context "option :#{option}", webmock: true do
30
56
  let(:random) { SecureRandom.random_number(1_000_000_000).to_s }
31
57
  let(:options) { { option.to_sym => random } }
32
58
 
33
- let(:token_response) { { access_token: {} }.to_json }
59
+ let(:token_response) { { access_token: {} } }
34
60
  let!(:request_token_stub) do
35
61
  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' })
62
+ with do |request|
63
+ request_body_includes(request, protocol, option, random)
64
+ end.to_return(
65
+ :status => 201,
66
+ :body => serialize(token_response, protocol),
67
+ :headers => { 'Content-Type' => content_type }
68
+ )
38
69
  end
39
70
 
40
71
  before { auth.request_token options }
@@ -55,11 +86,15 @@ describe "REST" do
55
86
  hmac_for(token_request, key_secret)
56
87
  end
57
88
 
58
- let(:token_response) { { access_token: {} }.to_json }
89
+ let(:token_response) { { access_token: {} } }
59
90
  let!(:request_token_stub) do
60
91
  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' })
92
+ with do |request|
93
+ request_body_includes(request, protocol, 'mac', mac)
94
+ end.to_return(
95
+ :status => 201,
96
+ :body => serialize(token_response, protocol),
97
+ :headers => { 'Content-Type' => content_type })
63
98
  end
64
99
 
65
100
  let!(:token) { auth.request_token(token_options) }
@@ -69,7 +104,7 @@ describe "REST" do
69
104
  end
70
105
  end
71
106
 
72
- context "with :query_time option" do
107
+ context 'with :query_time option' do
73
108
  let(:options) { { query_time: true } }
74
109
 
75
110
  it 'queries the server for the time' do
@@ -78,7 +113,7 @@ describe "REST" do
78
113
  end
79
114
  end
80
115
 
81
- context "without :query_time option" do
116
+ context 'without :query_time option' do
82
117
  let(:options) { { query_time: false } }
83
118
 
84
119
  it 'queries the server for the time' do
@@ -89,8 +124,8 @@ describe "REST" do
89
124
 
90
125
  context 'with :auth_url option', webmock: true do
91
126
  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 }
127
+ let(:token_request) { { id: key_id } }
128
+ let(:token_response) { { access_token: { } } }
94
129
  let(:query_params) { nil }
95
130
  let(:headers) { nil }
96
131
  let(:auth_method) { :get }
@@ -105,15 +140,24 @@ describe "REST" do
105
140
 
106
141
  let!(:auth_url_request_stub) do
107
142
  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' })
143
+ stub.with(:query => query_params) unless query_params.nil?
144
+ stub.with(:headers => headers) unless headers.nil?
145
+ stub.to_return(
146
+ :status => 201,
147
+ :body => token_request.to_json,
148
+ :headers => { 'Content-Type' => 'application/json' }
149
+ )
111
150
  end
112
151
 
113
152
  let!(:request_token_stub) do
114
153
  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' })
154
+ with do |request|
155
+ request_body_includes(request, protocol, 'id', key_id)
156
+ end.to_return(
157
+ :status => 201,
158
+ :body => serialize(token_response, protocol),
159
+ :headers => { 'Content-Type' => content_type }
160
+ )
117
161
  end
118
162
 
119
163
  context 'valid' do
@@ -244,30 +288,30 @@ describe "REST" do
244
288
  end
245
289
  end
246
290
 
247
- describe "#create_token_request" do
291
+ describe '#create_token_request' do
248
292
  let(:ttl) { 60 * 60 }
249
293
  let(:capability) { { :foo => ["publish"] } }
250
294
  let(:options) { Hash.new }
251
295
  subject { auth.create_token_request(options) }
252
296
 
253
- it "uses the key ID from the client" do
297
+ it 'uses the key ID from the client' do
254
298
  expect(subject[:id]).to eql(key_id)
255
299
  end
256
300
 
257
- it "uses the default TTL" do
301
+ it 'uses the default TTL' do
258
302
  expect(subject[:ttl]).to eql(Ably::Models::Token::DEFAULTS[:ttl])
259
303
  end
260
304
 
261
- it "uses the default capability" do
305
+ it 'uses the default capability' do
262
306
  expect(subject[:capability]).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
263
307
  end
264
308
 
265
- it "has a unique nonce" do
309
+ it 'has a unique nonce' do
266
310
  unique_nonces = 100.times.map { auth.create_token_request[:nonce] }
267
311
  expect(unique_nonces.uniq.length).to eql(100)
268
312
  end
269
313
 
270
- it "has a nonce of at least 16 characters" do
314
+ it 'has a nonce of at least 16 characters' do
271
315
  expect(subject[:nonce].length).to be >= 16
272
316
  end
273
317
 
@@ -283,7 +327,7 @@ describe "REST" do
283
327
  end
284
328
  end
285
329
 
286
- context "invalid attributes" do
330
+ context 'invalid attributes' do
287
331
  let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
288
332
  specify 'are ignored' do
289
333
  expect(subject.keys).to_not include(:is_not_used_by_token_request)
@@ -292,19 +336,19 @@ describe "REST" do
292
336
  end
293
337
  end
294
338
 
295
- context "missing key ID and/or secret" do
339
+ context 'missing key ID and/or secret' do
296
340
  let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
297
341
 
298
- it "should raise an exception if key secret is missing" do
342
+ it 'should raise an exception if key secret is missing' do
299
343
  expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
300
344
  end
301
345
 
302
- it "should raise an exception if key id is missing" do
346
+ it 'should raise an exception if key id is missing' do
303
347
  expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
304
348
  end
305
349
  end
306
350
 
307
- context "with :query_time option" do
351
+ context 'with :query_time option' do
308
352
  let(:time) { Time.now - 30 }
309
353
  let(:options) { { query_time: true } }
310
354
 
@@ -314,7 +358,7 @@ describe "REST" do
314
358
  end
315
359
  end
316
360
 
317
- context "with :timestamp option" do
361
+ context 'with :timestamp option' do
318
362
  let(:token_request_time) { Time.now + 5 }
319
363
  let(:options) { { timestamp: token_request_time } }
320
364
 
@@ -323,7 +367,7 @@ describe "REST" do
323
367
  end
324
368
  end
325
369
 
326
- context "signing" do
370
+ context 'signing' do
327
371
  let(:options) do
328
372
  {
329
373
  id: SecureRandom.hex,
@@ -342,7 +386,7 @@ describe "REST" do
342
386
  end
343
387
  end
344
388
 
345
- context "client with token authentication" do
389
+ context 'client with token authentication' do
346
390
  let(:capability) { { :foo => ["publish"] } }
347
391
 
348
392
  describe "with token_id argument" do
@@ -358,19 +402,19 @@ describe "REST" do
358
402
  Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
359
403
  end
360
404
 
361
- it "authenticates successfully" do
362
- expect(token_auth_client.channel("foo").publish("event", "data")).to be_truthy
405
+ it 'authenticates successfully' do
406
+ expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
363
407
  end
364
408
 
365
- it "disallows publishing on unspecified capability channels" do
366
- expect { token_auth_client.channel("bar").publish("event", "data") }.to raise_error do |error|
409
+ it 'disallows publishing on unspecified capability channels' do
410
+ expect { token_auth_client.channel('bar').publish('event', 'data') }.to raise_error do |error|
367
411
  expect(error).to be_a(Ably::Exceptions::InvalidToken)
368
412
  expect(error.status).to eql(401)
369
413
  expect(error.code).to eql(40160)
370
414
  end
371
415
  end
372
416
 
373
- it "fails if timestamp is invalid" do
417
+ it 'fails if timestamp is invalid' do
374
418
  expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
375
419
  expect(error).to be_a(Ably::Exceptions::InvalidToken)
376
420
  expect(error.status).to eql(401)
@@ -379,7 +423,7 @@ describe "REST" do
379
423
  end
380
424
  end
381
425
 
382
- describe "implicit through client id" do
426
+ describe 'implicit through client id' do
383
427
  let(:client_id) { '999' }
384
428
  let(:client) do
385
429
  Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment, protocol: protocol)
@@ -404,25 +448,25 @@ describe "REST" do
404
448
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
405
449
  end
406
450
 
407
- it "will create a token request" do
408
- client.channel("foo").publish("event", "data")
451
+ it 'will create a token request' do
452
+ client.channel('foo').publish('event', 'data')
409
453
  expect(request_token_stub).to have_been_requested
410
454
  end
411
455
  end
412
456
 
413
- context "will create a token" do
457
+ context 'will create a token' do
414
458
  let(:token) { client.auth.current_token }
415
459
 
416
- it "before a request is made" do
460
+ it 'before a request is made' do
417
461
  expect(token).to be_nil
418
462
  end
419
463
 
420
- it "when a message is published" do
421
- expect(client.channel("foo").publish("event", "data")).to be_truthy
464
+ it 'when a message is published' do
465
+ expect(client.channel('foo').publish('event', 'data')).to be_truthy
422
466
  end
423
467
 
424
- it "with capability and TTL defaults" do
425
- client.channel("foo").publish("event", "data")
468
+ it 'with capability and TTL defaults' do
469
+ client.channel('foo').publish('event', 'data')
426
470
 
427
471
  expect(token).to be_a(Ably::Models::Token)
428
472
  capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]