ably 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +185 -0
  4. data/LICENSE +15 -0
  5. data/README.md +8 -4
  6. data/SPEC.md +999 -531
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +1 -1
  9. data/lib/ably/auth.rb +114 -87
  10. data/lib/ably/exceptions.rb +40 -14
  11. data/lib/ably/models/message.rb +3 -5
  12. data/lib/ably/models/paginated_result.rb +3 -12
  13. data/lib/ably/models/presence_message.rb +8 -2
  14. data/lib/ably/models/protocol_message.rb +15 -3
  15. data/lib/ably/models/stat.rb +1 -1
  16. data/lib/ably/models/token_details.rb +1 -1
  17. data/lib/ably/modules/channels_collection.rb +7 -1
  18. data/lib/ably/modules/conversions.rb +1 -1
  19. data/lib/ably/modules/encodeable.rb +6 -3
  20. data/lib/ably/modules/message_pack.rb +2 -2
  21. data/lib/ably/modules/model_common.rb +1 -1
  22. data/lib/ably/modules/state_machine.rb +2 -2
  23. data/lib/ably/realtime.rb +1 -0
  24. data/lib/ably/realtime/auth.rb +191 -0
  25. data/lib/ably/realtime/channel.rb +97 -25
  26. data/lib/ably/realtime/channel/channel_manager.rb +11 -3
  27. data/lib/ably/realtime/client.rb +22 -6
  28. data/lib/ably/realtime/connection.rb +74 -41
  29. data/lib/ably/realtime/connection/connection_manager.rb +48 -33
  30. data/lib/ably/realtime/presence.rb +17 -3
  31. data/lib/ably/rest/channel.rb +43 -16
  32. data/lib/ably/rest/client.rb +57 -26
  33. data/lib/ably/rest/middleware/exceptions.rb +3 -1
  34. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -2
  35. data/lib/ably/rest/presence.rb +1 -0
  36. data/lib/ably/version.rb +1 -1
  37. data/spec/acceptance/realtime/auth_spec.rb +242 -0
  38. data/spec/acceptance/realtime/channel_spec.rb +277 -5
  39. data/spec/acceptance/realtime/channels_spec.rb +64 -0
  40. data/spec/acceptance/realtime/client_spec.rb +26 -5
  41. data/spec/acceptance/realtime/connection_failures_spec.rb +23 -6
  42. data/spec/acceptance/realtime/connection_spec.rb +167 -16
  43. data/spec/acceptance/realtime/message_spec.rb +9 -8
  44. data/spec/acceptance/realtime/presence_history_spec.rb +1 -0
  45. data/spec/acceptance/realtime/presence_spec.rb +121 -10
  46. data/spec/acceptance/realtime/stats_spec.rb +13 -1
  47. data/spec/acceptance/rest/auth_spec.rb +161 -79
  48. data/spec/acceptance/rest/base_spec.rb +3 -3
  49. data/spec/acceptance/rest/channel_spec.rb +142 -15
  50. data/spec/acceptance/rest/channels_spec.rb +23 -0
  51. data/spec/acceptance/rest/client_spec.rb +180 -18
  52. data/spec/acceptance/rest/message_spec.rb +8 -8
  53. data/spec/acceptance/rest/presence_spec.rb +136 -25
  54. data/spec/acceptance/rest/stats_spec.rb +60 -4
  55. data/spec/shared/client_initializer_behaviour.rb +54 -3
  56. data/spec/unit/auth_spec.rb +7 -6
  57. data/spec/unit/models/message_spec.rb +1 -9
  58. data/spec/unit/models/paginated_result_spec.rb +1 -18
  59. data/spec/unit/models/presence_message_spec.rb +1 -1
  60. data/spec/unit/models/protocol_message_spec.rb +21 -1
  61. data/spec/unit/realtime/channel_spec.rb +10 -3
  62. data/spec/unit/realtime/channels_spec.rb +27 -8
  63. data/spec/unit/rest/channel_spec.rb +0 -8
  64. data/spec/unit/rest/client_spec.rb +7 -7
  65. metadata +13 -7
  66. data/LICENSE.txt +0 -22
@@ -264,7 +264,7 @@ describe Ably::Realtime::Channel, :event_machine do
264
264
  channel.attach do
265
265
  channel.transition_state_machine :failed, RuntimeError.new
266
266
  expect(channel).to be_failed
267
- expect { channel.detach }.to raise_error Ably::Exceptions::StateChangeError
267
+ expect { channel.detach }.to raise_error Ably::Exceptions::InvalidStateChange
268
268
  stop_reactor
269
269
  end
270
270
  end
@@ -304,6 +304,24 @@ describe Ably::Realtime::Channel, :event_machine do
304
304
  end
305
305
  end
306
306
  end
307
+
308
+ context 'when state is :initialized' do
309
+ it 'does nothing as there is no channel to detach' do
310
+ expect(channel).to be_initialized
311
+ channel.detach do
312
+ expect(channel).to be_initialized
313
+ stop_reactor
314
+ end
315
+ end
316
+
317
+ it 'returns a valid deferrable' do
318
+ expect(channel).to be_initialized
319
+ channel.detach.callback do
320
+ expect(channel).to be_initialized
321
+ stop_reactor
322
+ end
323
+ end
324
+ end
307
325
  end
308
326
 
309
327
  describe 'channel recovery in :attaching state' do
@@ -315,6 +333,9 @@ describe Ably::Realtime::Channel, :event_machine do
315
333
  end
316
334
 
317
335
  context '#publish' do
336
+ let(:name) { random_str }
337
+ let(:data) { random_str }
338
+
318
339
  context 'when attached' do
319
340
  it 'publishes messages' do
320
341
  channel.attach do
@@ -354,6 +375,197 @@ describe Ably::Realtime::Channel, :event_machine do
354
375
  stop_reactor
355
376
  end
356
377
  end
378
+
379
+ context 'with :queue_messages client option set to false' do
380
+ let(:client_options) { default_options.merge(queue_messages: false) }
381
+
382
+ context 'and connection state initialized' do
383
+ it 'raises an exception' do
384
+ expect { channel.publish('event') }.to raise_error Ably::Exceptions::MessageQueueingDisabled
385
+ expect(client.connection).to be_initialized
386
+ stop_reactor
387
+ end
388
+ end
389
+
390
+ context 'and connection state connecting' do
391
+ it 'raises an exception' do
392
+ client.connect
393
+ EventMachine.next_tick do
394
+ expect { channel.publish('event') }.to raise_error Ably::Exceptions::MessageQueueingDisabled
395
+ expect(client.connection).to be_connecting
396
+ stop_reactor
397
+ end
398
+ end
399
+ end
400
+
401
+ context 'and connection state disconnected' do
402
+ let(:client_options) { default_options.merge(queue_messages: false, :log_level => :error ) }
403
+ it 'raises an exception' do
404
+ client.connection.once(:connected) do
405
+ client.connection.once(:disconnected) do
406
+ expect { channel.publish('event') }.to raise_error Ably::Exceptions::MessageQueueingDisabled
407
+ expect(client.connection).to be_disconnected
408
+ stop_reactor
409
+ end
410
+ client.connection.transition_state_machine :disconnected
411
+ end
412
+ end
413
+ end
414
+
415
+ context 'and connection state connected' do
416
+ it 'publishes the message' do
417
+ client.connection.once(:connected) do
418
+ channel.publish('event')
419
+ stop_reactor
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+
426
+ context 'with name and data arguments' do
427
+ it 'publishes the message and return true indicating success' do
428
+ channel.publish(name, data) do
429
+ channel.history do |page|
430
+ expect(page.items.first.name).to eql(name)
431
+ expect(page.items.first.data).to eql(data)
432
+ stop_reactor
433
+ end
434
+ end
435
+ end
436
+ end
437
+
438
+ context 'with an array of Hash objects with :name and :data attributes' do
439
+ let(:messages) do
440
+ 10.times.map do |index|
441
+ { name: index.to_s, data: { "index" => index + 10 } }
442
+ end
443
+ end
444
+
445
+ it 'publishes an array of messages in one ProtocolMessage' do
446
+ published = false
447
+
448
+ channel.attach do
449
+ client.connection.__outgoing_protocol_msgbus__.once(:protocol_message) do |protocol_message|
450
+ expect(protocol_message.messages.count).to eql(messages.count)
451
+ published = true
452
+ end
453
+
454
+ channel.publish(messages).callback do
455
+ channel.history do |page|
456
+ expect(page.items.map(&:name)).to match_array(messages.map { |message| message[:name] })
457
+ expect(page.items.map(&:data)).to match_array(messages.map { |message| message[:data] })
458
+ expect(published).to eql(true)
459
+ stop_reactor
460
+ end
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ context 'with an array of Message objects' do
467
+ let(:messages) do
468
+ 10.times.map do |index|
469
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
470
+ end
471
+ end
472
+
473
+ it 'publishes an array of messages in one ProtocolMessage' do
474
+ published = false
475
+
476
+ channel.attach do
477
+ client.connection.__outgoing_protocol_msgbus__.once(:protocol_message) do |protocol_message|
478
+ expect(protocol_message.messages.count).to eql(messages.count)
479
+ published = true
480
+ end
481
+
482
+ channel.publish(messages).callback do
483
+ channel.history do |page|
484
+ expect(page.items.map(&:name)).to match_array(messages.map { |message| message[:name] })
485
+ expect(page.items.map(&:data)).to match_array(messages.map { |message| message[:data] })
486
+ expect(published).to eql(true)
487
+ stop_reactor
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ context 'with two invalid message out of 12' do
494
+ let(:client_options) { default_options.merge(client_id: 'valid') }
495
+ let(:invalid_messages) do
496
+ 2.times.map do |index|
497
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 }, client_id: 'prohibited')
498
+ end
499
+ end
500
+
501
+ it 'calls the errback once' do
502
+ skip 'Waiting for issue #256 to be resolved'
503
+ channel.publish(messages + invalid_messages).tap do |deferrable|
504
+ deferrable.callback do
505
+ raise 'Publish should have failed'
506
+ end
507
+
508
+ deferrable.errback do |error, message|
509
+ # TODO: Review whether we should fail once or multiple times
510
+ channel.history do |page|
511
+ expect(page.items.count).to eql(0)
512
+ stop_reactor
513
+ end
514
+ end
515
+ end
516
+ end
517
+ end
518
+
519
+ context 'only invalid messages' do
520
+ let(:client_options) { default_options.merge(client_id: 'valid') }
521
+ let(:invalid_messages) do
522
+ 10.times.map do |index|
523
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 }, client_id: 'prohibited')
524
+ end
525
+ end
526
+
527
+ it 'calls the errback once' do
528
+ skip 'Waiting for issue #256 to be resolved'
529
+ channel.publish(invalid_messages).tap do |deferrable|
530
+ deferrable.callback do
531
+ raise 'Publish should have failed'
532
+ end
533
+
534
+ deferrable.errback do |error, message|
535
+ channel.history do |page|
536
+ expect(page.items.count).to eql(0)
537
+ stop_reactor
538
+ end
539
+ end
540
+ end
541
+ end
542
+ end
543
+ end
544
+
545
+ context 'with many many messages and many connections simultaneously' do
546
+ let(:connection_count) { 5 }
547
+ let(:messages) { 5.times.map { |index| { name: "test", data: "message-#{index}" } } }
548
+ let(:published) { [] }
549
+ let(:channel_name) { random_str }
550
+
551
+ it 'publishes all messages, all success callbacks are called, and a history request confirms all messages were published' do
552
+ connection_count.times.map do
553
+ Ably::Realtime::Client.new(client_options)
554
+ end.each do |client|
555
+ channel = client.channels.get(channel_name)
556
+ messages.each do |message|
557
+ channel.publish(message.fetch(:name), message.fetch(:data)) do
558
+ published << message
559
+ if published.count == connection_count * messages.count
560
+ channel.history do |history_page|
561
+ expect(history_page.items.count).to eql(connection_count * messages.count)
562
+ stop_reactor
563
+ end
564
+ end
565
+ end
566
+ end
567
+ end
568
+ end
357
569
  end
358
570
  end
359
571
 
@@ -368,6 +580,20 @@ describe Ably::Realtime::Channel, :event_machine do
368
580
  end
369
581
  end
370
582
 
583
+ context 'before attach' do
584
+ it 'receives messages as soon as attached' do
585
+ channel.subscribe('click') do |message|
586
+ expect(channel).to be_attached
587
+ expect(message.data).to eql('data')
588
+ stop_reactor
589
+ end
590
+
591
+ channel.publish('click', 'data')
592
+
593
+ expect(channel).to be_attaching
594
+ end
595
+ end
596
+
371
597
  context 'with no event argument' do
372
598
  it 'subscribes for all events' do
373
599
  channel.subscribe do |message|
@@ -430,7 +656,7 @@ describe Ably::Realtime::Channel, :event_machine do
430
656
 
431
657
  context 'when connection state changes to' do
432
658
  context ':failed' do
433
- let(:connection_error) { Ably::Exceptions::ConnectionError.new('forced failure', 500, 50000) }
659
+ let(:connection_error) { Ably::Exceptions::ConnectionFailed.new('forced failure', 500, 50000) }
434
660
  let(:client_options) { default_options.merge(log_level: :none) }
435
661
 
436
662
  def fake_error(error)
@@ -441,7 +667,8 @@ describe Ably::Realtime::Channel, :event_machine do
441
667
  it 'transitions state to :failed' do
442
668
  channel.attach do
443
669
  channel.on(:failed) do |error|
444
- expect(error).to eql(connection_error)
670
+ expect(error).to be_a(Ably::Exceptions::ConnectionFailed)
671
+ expect(error.code).to eql(80002)
445
672
  stop_reactor
446
673
  end
447
674
  fake_error connection_error
@@ -451,7 +678,8 @@ describe Ably::Realtime::Channel, :event_machine do
451
678
  it 'emits an error event on the channel' do
452
679
  channel.attach do
453
680
  channel.on(:error) do |error|
454
- expect(error).to eql(connection_error)
681
+ expect(error).to be_a(Ably::Exceptions::ConnectionFailed)
682
+ expect(error.code).to eql(80002)
455
683
  stop_reactor
456
684
  end
457
685
  fake_error connection_error
@@ -461,7 +689,8 @@ describe Ably::Realtime::Channel, :event_machine do
461
689
  it 'updates the channel error_reason' do
462
690
  channel.attach do
463
691
  channel.on(:failed) do |error|
464
- expect(channel.error_reason).to eql(connection_error)
692
+ expect(error).to be_a(Ably::Exceptions::ConnectionFailed)
693
+ expect(error.code).to eql(80002)
465
694
  stop_reactor
466
695
  end
467
696
  fake_error connection_error
@@ -509,6 +738,18 @@ describe Ably::Realtime::Channel, :event_machine do
509
738
  end
510
739
  end
511
740
  end
741
+
742
+ context 'a channel ATTACH request' do
743
+ it 'raises an exception' do
744
+ client.connect do
745
+ client.connection.once(:failed) do
746
+ expect { channel.attach }.to raise_error Ably::Exceptions::InvalidStateChange
747
+ stop_reactor
748
+ end
749
+ fake_error connection_error
750
+ end
751
+ end
752
+ end
512
753
  end
513
754
 
514
755
  context ':closed' do
@@ -564,6 +805,37 @@ describe Ably::Realtime::Channel, :event_machine do
564
805
  end
565
806
  end
566
807
  end
808
+
809
+ context 'a channel ATTACH request when connection CLOSED' do
810
+ it 'raises an exception' do
811
+ client.connect do
812
+ client.connection.once(:closed) do
813
+ expect { channel.attach }.to raise_error Ably::Exceptions::InvalidStateChange
814
+ stop_reactor
815
+ end
816
+ client.close
817
+ end
818
+ end
819
+ end
820
+
821
+ context 'a channel ATTACH request when connection CLOSING' do
822
+ it 'raises an exception' do
823
+ client.connect do
824
+ client.connection.once(:closing) do
825
+ expect { channel.attach }.to raise_error Ably::Exceptions::InvalidStateChange
826
+ stop_reactor
827
+ end
828
+ client.close
829
+ end
830
+ end
831
+ end
832
+ end
833
+ end
834
+
835
+ describe '#presence' do
836
+ it 'returns a Ably::Realtime::Presence object' do
837
+ expect(channel.presence).to be_a(Ably::Realtime::Presence)
838
+ stop_reactor
567
839
  end
568
840
  end
569
841
  end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Realtime::Channels do
5
+ shared_examples 'a channel' do
6
+ it 'returns a channel object' do
7
+ expect(channel).to be_a Ably::Realtime::Channel
8
+ expect(channel.name).to eql(channel_name)
9
+ end
10
+
11
+ it 'returns channel object and passes the provided options' do
12
+ expect(channel_with_options.options).to eql(options)
13
+ end
14
+ end
15
+
16
+ vary_by_protocol do
17
+ let(:client) do
18
+ Ably::Realtime::Client.new(key: api_key, environment: environment, protocol: protocol)
19
+ end
20
+ let(:channel_name) { random_str }
21
+ let(:options) { { key: 'value' } }
22
+
23
+ describe 'using shortcut method #channel on the client object' do
24
+ let(:channel) { client.channel(channel_name) }
25
+ let(:channel_with_options) { client.channel(channel_name, options) }
26
+ it_behaves_like 'a channel'
27
+ end
28
+
29
+ describe 'using #get method on client#channels' do
30
+ let(:channel) { client.channels.get(channel_name) }
31
+ let(:channel_with_options) { client.channels.get(channel_name, options) }
32
+ it_behaves_like 'a channel'
33
+ end
34
+
35
+ describe 'accessing an existing channel object with different options' do
36
+ let(:new_channel_options) { { encrypted: true } }
37
+ let(:original_channel) { client.channels.get(channel_name, options) }
38
+
39
+ it 'overrides the existing channel options and returns the channel object' do
40
+ expect(original_channel.options).to_not include(:encrypted)
41
+ new_channel = client.channels.get(channel_name, new_channel_options)
42
+ expect(new_channel).to be_a(Ably::Realtime::Channel)
43
+ expect(new_channel.options[:encrypted]).to eql(true)
44
+ end
45
+ end
46
+
47
+ describe 'accessing an existing channel object without specifying any channel options' do
48
+ let(:original_channel) { client.channels.get(channel_name, options) }
49
+
50
+ it 'returns the existing channel without modifying the channel options' do
51
+ expect(original_channel.options).to eql(options)
52
+ new_channel = client.channels.get(channel_name)
53
+ expect(new_channel).to be_a(Ably::Realtime::Channel)
54
+ expect(original_channel.options).to eql(options)
55
+ end
56
+ end
57
+
58
+ describe 'using undocumented array accessor [] method on client#channels' do
59
+ let(:channel) { client.channels[channel_name] }
60
+ let(:channel_with_options) { client.channels[channel_name, options] }
61
+ it_behaves_like 'a channel'
62
+ end
63
+ end
64
+ end
@@ -9,7 +9,7 @@ describe Ably::Realtime::Client, :event_machine do
9
9
 
10
10
  let(:client_options) { default_options }
11
11
  let(:connection) { subject.connection }
12
- let(:auth_params) { subject.auth.auth_params }
12
+ let(:auth_params) { subject.auth.auth_params_sync }
13
13
 
14
14
  subject { Ably::Realtime::Client.new(client_options) }
15
15
 
@@ -25,14 +25,14 @@ describe Ably::Realtime::Client, :event_machine do
25
25
  end
26
26
 
27
27
  context ':tls option' do
28
- context 'set to false to forec a plain-text connection' do
28
+ context 'set to false to force a plain-text connection' do
29
29
  let(:client_options) { default_options.merge(tls: false, log_level: :none) }
30
30
 
31
31
  it 'fails to connect because a private key cannot be sent over a non-secure connection' do
32
32
  connection.on(:connected) { raise 'Should not have connected' }
33
33
 
34
34
  connection.on(:failed) do |error|
35
- expect(error).to be_a(Ably::Exceptions::InsecureRequestError)
35
+ expect(error).to be_a(Ably::Exceptions::InsecureRequest)
36
36
  stop_reactor
37
37
  end
38
38
  end
@@ -44,7 +44,7 @@ describe Ably::Realtime::Client, :event_machine do
44
44
  [true, false].each do |tls_enabled|
45
45
  context "with TLS #{tls_enabled ? 'enabled' : 'disabled'}" do
46
46
  let(:capability) { { :foo => ["publish"] } }
47
- let(:token_details) { Ably::Realtime::Client.new(default_options).auth.request_token(capability: capability) }
47
+ let(:token_details) { Ably::Realtime::Client.new(default_options).auth.request_token_sync(capability: capability) }
48
48
  let(:client_options) { default_options.merge(token: token_details.token) }
49
49
 
50
50
  context 'and a pre-generated Token provided with the :token option' do
@@ -95,7 +95,7 @@ describe Ably::Realtime::Client, :event_machine do
95
95
  subject do
96
96
  Ably::Realtime::Client.new(client_options.merge(auth_callback: Proc.new do
97
97
  @block_called = true
98
- auth.create_token_request(client_id: client_id)
98
+ auth.create_token_request_sync(client_id: client_id)
99
99
  end))
100
100
  end
101
101
 
@@ -115,5 +115,26 @@ describe Ably::Realtime::Client, :event_machine do
115
115
  end
116
116
  end
117
117
  end
118
+
119
+ context '#connection' do
120
+ it 'provides access to the Connection object' do
121
+ expect(subject.connection).to be_a(Ably::Realtime::Connection)
122
+ stop_reactor
123
+ end
124
+ end
125
+
126
+ context '#channels' do
127
+ it 'provides access to the Channels collection object' do
128
+ expect(subject.channels).to be_a(Ably::Realtime::Channels)
129
+ stop_reactor
130
+ end
131
+ end
132
+
133
+ context '#auth' do
134
+ it 'provides access to the Realtime::Auth object' do
135
+ expect(subject.auth).to be_a(Ably::Realtime::Auth)
136
+ stop_reactor
137
+ end
138
+ end
118
139
  end
119
140
  end