ably 0.1.6 → 0.2.0

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 (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]