ably 0.8.5 → 0.8.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +42 -48
  4. data/SPEC.md +1099 -640
  5. data/ably.gemspec +10 -4
  6. data/lib/ably/auth.rb +155 -47
  7. data/lib/ably/exceptions.rb +2 -0
  8. data/lib/ably/models/channel_state_change.rb +2 -3
  9. data/lib/ably/models/connection_details.rb +54 -0
  10. data/lib/ably/models/protocol_message.rb +14 -4
  11. data/lib/ably/models/token_details.rb +13 -7
  12. data/lib/ably/models/token_request.rb +1 -2
  13. data/lib/ably/modules/ably.rb +3 -2
  14. data/lib/ably/modules/message_emitter.rb +1 -3
  15. data/lib/ably/modules/state_emitter.rb +2 -2
  16. data/lib/ably/realtime/auth.rb +6 -0
  17. data/lib/ably/realtime/channel/channel_manager.rb +2 -0
  18. data/lib/ably/realtime/channel.rb +15 -4
  19. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  20. data/lib/ably/realtime/client.rb +10 -3
  21. data/lib/ably/realtime/connection/connection_manager.rb +58 -54
  22. data/lib/ably/realtime/connection.rb +62 -6
  23. data/lib/ably/realtime/presence.rb +18 -5
  24. data/lib/ably/rest/channel.rb +9 -1
  25. data/lib/ably/rest/client.rb +32 -14
  26. data/lib/ably/rest/presence.rb +1 -1
  27. data/lib/ably/version.rb +1 -1
  28. data/lib/ably.rb +2 -0
  29. data/spec/acceptance/realtime/auth_spec.rb +251 -11
  30. data/spec/acceptance/realtime/channel_history_spec.rb +12 -2
  31. data/spec/acceptance/realtime/channel_spec.rb +316 -24
  32. data/spec/acceptance/realtime/client_spec.rb +93 -1
  33. data/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
  34. data/spec/acceptance/realtime/connection_spec.rb +284 -60
  35. data/spec/acceptance/realtime/message_spec.rb +45 -6
  36. data/spec/acceptance/realtime/presence_history_spec.rb +4 -0
  37. data/spec/acceptance/realtime/presence_spec.rb +181 -49
  38. data/spec/acceptance/realtime/time_spec.rb +13 -0
  39. data/spec/acceptance/rest/auth_spec.rb +222 -4
  40. data/spec/acceptance/rest/channel_spec.rb +132 -1
  41. data/spec/acceptance/rest/client_spec.rb +129 -28
  42. data/spec/acceptance/rest/presence_spec.rb +7 -7
  43. data/spec/acceptance/rest/time_spec.rb +10 -0
  44. data/spec/shared/client_initializer_behaviour.rb +41 -17
  45. data/spec/spec_helper.rb +1 -0
  46. data/spec/support/debug_failure_helper.rb +16 -0
  47. data/spec/unit/models/connection_details_spec.rb +60 -0
  48. data/spec/unit/models/protocol_message_spec.rb +45 -0
  49. data/spec/unit/modules/event_emitter_spec.rb +3 -1
  50. data/spec/unit/realtime/channel_spec.rb +6 -5
  51. data/spec/unit/realtime/client_spec.rb +5 -1
  52. data/spec/unit/realtime/connection_spec.rb +5 -1
  53. data/spec/unit/realtime/realtime_spec.rb +5 -1
  54. metadata +54 -7
@@ -504,55 +504,143 @@ describe Ably::Realtime::Channel, :event_machine do
504
504
  end
505
505
  end
506
506
 
507
+ context 'nil attributes' do
508
+ context 'when name is nil' do
509
+ let(:data) { random_str }
510
+
511
+ it 'publishes the message without a name attribute in the payload' do
512
+ published = false
513
+ channel.publish(nil, data) do
514
+ published = true
515
+ end
516
+
517
+ channel.subscribe do |message|
518
+ expect(message.name).to be_nil
519
+ channel.history do |page|
520
+ expect(page.items.first.name).to be_nil
521
+ expect(page.items.first.data).to eql(data)
522
+ EM.add_timer(0.5) do
523
+ expect(published).to eql(true)
524
+ stop_reactor
525
+ end
526
+ end
527
+ end
528
+ end
529
+ end
530
+
531
+ context 'when data is nil' do
532
+ let(:name) { random_str }
533
+
534
+ it 'publishes the message without a data attribute in the payload' do
535
+ published = false
536
+ channel.publish(name, nil) do
537
+ published = true
538
+ end
539
+
540
+ channel.subscribe do |message|
541
+ expect(message.data).to be_nil
542
+ channel.history do |page|
543
+ expect(page.items.first.name).to eql(name)
544
+ expect(page.items.first.data).to be_nil
545
+ EM.add_timer(0.5) do
546
+ expect(published).to eql(true)
547
+ stop_reactor
548
+ end
549
+ end
550
+ end
551
+ end
552
+ end
553
+
554
+ context 'with neither name or data attributes' do
555
+ let(:name) { random_str }
556
+
557
+ it 'publishes the message without any attributes in the payload' do
558
+ channel.publish(nil) do
559
+ channel.history do |page|
560
+ expect(page.items.first.name).to be_nil
561
+ expect(page.items.first.data).to be_nil
562
+ stop_reactor
563
+ end
564
+ end
565
+ end
566
+ end
567
+ end
568
+
507
569
  context 'with two invalid message out of 12' do
508
- let(:client_options) { default_options.merge(client_id: 'valid') }
570
+ let(:rest_client) { Ably::Rest::Client.new(default_options.merge(client_id: 'valid')) }
571
+
509
572
  let(:invalid_messages) do
510
573
  2.times.map do |index|
511
574
  Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 }, client_id: 'prohibited')
512
575
  end
513
576
  end
514
577
 
515
- it 'calls the errback once' do
516
- skip 'Waiting for issue #256 to be resolved'
517
- channel.publish(messages + invalid_messages).tap do |deferrable|
518
- deferrable.callback do
519
- raise 'Publish should have failed'
520
- end
578
+ context 'before client_id is known (validated)' do
579
+ let(:client_options) { default_options.merge(token: rest_client.auth.request_token.token, log_level: :error) }
521
580
 
522
- deferrable.errback do |error, message|
523
- # TODO: Review whether we should fail once or multiple times
524
- channel.history do |page|
525
- expect(page.items.count).to eql(0)
526
- stop_reactor
581
+ it 'calls the errback once' do
582
+ channel.publish(messages + invalid_messages).tap do |deferrable|
583
+ deferrable.callback do
584
+ raise 'Publish should have failed'
585
+ end
586
+
587
+ deferrable.errback do |error, message|
588
+ # TODO: Review whether we should fail once or multiple times
589
+ channel.history do |page|
590
+ expect(page.items.count).to eql(0)
591
+ stop_reactor
592
+ end
527
593
  end
528
594
  end
529
595
  end
530
596
  end
597
+
598
+ context 'when client_id is known (validated)' do
599
+ let(:client_options) { default_options.merge(client_id: 'valid') }
600
+
601
+ it 'raises an exception' do
602
+ expect { channel.publish(messages + invalid_messages) }.to raise_error Ably::Exceptions::IncompatibleClientId
603
+ stop_reactor
604
+ end
605
+ end
531
606
  end
532
607
 
533
608
  context 'only invalid messages' do
534
- let(:client_options) { default_options.merge(client_id: 'valid') }
609
+ let(:rest_client) { Ably::Rest::Client.new(default_options.merge(client_id: 'valid')) }
610
+
535
611
  let(:invalid_messages) do
536
612
  10.times.map do |index|
537
613
  Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 }, client_id: 'prohibited')
538
614
  end
539
615
  end
540
616
 
541
- it 'calls the errback once' do
542
- skip 'Waiting for issue #256 to be resolved'
543
- channel.publish(invalid_messages).tap do |deferrable|
544
- deferrable.callback do
545
- raise 'Publish should have failed'
546
- end
617
+ context 'before client_id is known (validated)' do
618
+ let(:client_options) { default_options.merge(token: rest_client.auth.request_token.token, log_level: :error) }
547
619
 
548
- deferrable.errback do |error, message|
549
- channel.history do |page|
550
- expect(page.items.count).to eql(0)
551
- stop_reactor
620
+ it 'calls the errback once' do
621
+ channel.publish(invalid_messages).tap do |deferrable|
622
+ deferrable.callback do
623
+ raise 'Publish should have failed'
624
+ end
625
+
626
+ deferrable.errback do |error, message|
627
+ channel.history do |page|
628
+ expect(page.items.count).to eql(0)
629
+ stop_reactor
630
+ end
552
631
  end
553
632
  end
554
633
  end
555
634
  end
635
+
636
+ context 'when client_id is known (validated)' do
637
+ let(:client_options) { default_options.merge(client_id: 'valid') }
638
+
639
+ it 'raises an exception' do
640
+ expect { channel.publish(invalid_messages) }.to raise_error Ably::Exceptions::IncompatibleClientId
641
+ stop_reactor
642
+ end
643
+ end
556
644
  end
557
645
  end
558
646
 
@@ -581,6 +669,208 @@ describe Ably::Realtime::Channel, :event_machine do
581
669
  end
582
670
  end
583
671
  end
672
+
673
+ context 'identified clients' do
674
+ context 'when authenticated with a wildcard client_id' do
675
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*') }
676
+ let(:client_options) { default_options.merge(key: nil, token: token) }
677
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
678
+ let(:channel) { client.channels.get(channel_name) }
679
+
680
+ context 'with a valid client_id in the message' do
681
+ it 'succeeds' do
682
+ channel.publish([name: 'event', client_id: 'validClient']).tap do |deferrable|
683
+ deferrable.errback { raise 'Should have succeeded' }
684
+ end
685
+ channel.subscribe('event') do |message|
686
+ expect(message.client_id).to eql('validClient')
687
+ EM.add_timer(0.5) { stop_reactor }
688
+ end
689
+ end
690
+ end
691
+
692
+ context 'with a wildcard client_id in the message' do
693
+ it 'throws an exception' do
694
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
695
+ stop_reactor
696
+ end
697
+ end
698
+
699
+ context 'with an empty client_id in the message' do
700
+ it 'succeeds and publishes without a client_id' do
701
+ channel.publish([name: 'event', client_id: nil]).tap do |deferrable|
702
+ deferrable.errback { raise 'Should have succeeded' }
703
+ end
704
+ channel.subscribe('event') do |message|
705
+ expect(message.client_id).to be_nil
706
+ EM.add_timer(0.5) { stop_reactor }
707
+ end
708
+ end
709
+ end
710
+ end
711
+
712
+ context 'when authenticated with a Token string with an implicit client_id' do
713
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: 'valid').token }
714
+ let(:client_options) { default_options.merge(key: nil, token: token) }
715
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
716
+ let(:channel) { client.channels.get(channel_name) }
717
+
718
+ context 'before the client is CONNECTED and the client\'s identity has been obtained' do
719
+ context 'with a valid client_id in the message' do
720
+ it 'succeeds' do
721
+ channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable|
722
+ deferrable.errback { raise 'Should have succeeded' }
723
+ end
724
+ channel.subscribe('event') do |message|
725
+ expect(message.client_id).to eql('valid')
726
+ EM.add_timer(0.5) { stop_reactor }
727
+ end
728
+ end
729
+ end
730
+
731
+ context 'with an invalid client_id in the message' do
732
+ let(:client_options) { default_options.merge(key: nil, token: token, log_level: :error) }
733
+ it 'succeeds in the client library but then fails when delivered to Ably' do
734
+ channel.publish([name: 'event', client_id: 'invalid']).tap do |deferrable|
735
+ EM.add_timer(0.5) { stop_reactor }
736
+ end
737
+ channel.subscribe('event') do |message|
738
+ raise 'Message should not have been published'
739
+ end
740
+ end
741
+ end
742
+
743
+ context 'with an empty client_id in the message' do
744
+ it 'succeeds and publishes with an implicit client_id' do
745
+ channel.publish([name: 'event', client_id: nil]).tap do |deferrable|
746
+ deferrable.errback { raise 'Should have succeeded' }
747
+ end
748
+ channel.subscribe('event') do |message|
749
+ expect(message.client_id).to eql('valid')
750
+ EM.add_timer(0.5) { stop_reactor }
751
+ end
752
+ end
753
+ end
754
+ end
755
+
756
+ context 'after the client is CONNECTED and the client\'s identity is known' do
757
+ context 'with a valid client_id in the message' do
758
+ it 'succeeds' do
759
+ client.connection.once(:connected) do
760
+ channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable|
761
+ deferrable.errback { raise 'Should have succeeded' }
762
+ end
763
+ channel.subscribe('event') do |message|
764
+ expect(message.client_id).to eql('valid')
765
+ EM.add_timer(0.5) { stop_reactor }
766
+ end
767
+ end
768
+ end
769
+ end
770
+
771
+ context 'with an invalid client_id in the message' do
772
+ it 'throws an exception' do
773
+ client.connection.once(:connected) do
774
+ expect { channel.publish([name: 'event', client_id: 'invalid']) }.to raise_error Ably::Exceptions::IncompatibleClientId
775
+ stop_reactor
776
+ end
777
+ end
778
+ end
779
+
780
+ context 'with an empty client_id in the message' do
781
+ it 'succeeds and publishes with an implicit client_id' do
782
+ client.connection.once(:connected) do
783
+ channel.publish([name: 'event', client_id: nil]).tap do |deferrable|
784
+ deferrable.errback { raise 'Should have succeeded' }
785
+ end
786
+ channel.subscribe('event') do |message|
787
+ expect(message.client_id).to eql('valid')
788
+ EM.add_timer(0.5) { stop_reactor }
789
+ end
790
+ end
791
+ end
792
+ end
793
+ end
794
+ end
795
+
796
+ context 'when authenticated with a valid client_id' do
797
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: 'valid') }
798
+ let(:client_options) { default_options.merge(key: nil, token: token) }
799
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
800
+ let(:channel) { client.channels.get(channel_name) }
801
+
802
+ context 'with a valid client_id' do
803
+ it 'succeeds' do
804
+ channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable|
805
+ deferrable.errback { raise 'Should have succeeded' }
806
+ end
807
+ channel.subscribe('event') do |message|
808
+ expect(message.client_id).to eql('valid')
809
+ EM.add_timer(0.5) { stop_reactor }
810
+ end
811
+ end
812
+ end
813
+
814
+ context 'with a wildcard client_id in the message' do
815
+ it 'throws an exception' do
816
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
817
+ stop_reactor
818
+ end
819
+ end
820
+
821
+ context 'with an invalid client_id in the message' do
822
+ it 'throws an exception' do
823
+ expect { channel.publish([name: 'event', client_id: 'invalid']) }.to raise_error Ably::Exceptions::IncompatibleClientId
824
+ stop_reactor
825
+ end
826
+ end
827
+
828
+ context 'with an empty client_id in the message' do
829
+ it 'succeeds and publishes with an implicit client_id' do
830
+ channel.publish([name: 'event', client_id: nil]).tap do |deferrable|
831
+ deferrable.errback { raise 'Should have succeeded' }
832
+ end
833
+ channel.subscribe('event') do |message|
834
+ expect(message.client_id).to eql('valid')
835
+ EM.add_timer(0.5) { stop_reactor }
836
+ end
837
+ end
838
+ end
839
+ end
840
+
841
+ context 'when anonymous and no client_id' do
842
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: nil) }
843
+ let(:client_options) { default_options.merge(key: nil, token: token) }
844
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
845
+ let(:channel) { client.channels.get(channel_name) }
846
+
847
+ context 'with a client_id in the message' do
848
+ it 'throws an exception' do
849
+ expect { channel.publish([name: 'event', client_id: 'invalid']) }.to raise_error Ably::Exceptions::IncompatibleClientId
850
+ stop_reactor
851
+ end
852
+ end
853
+
854
+ context 'with a wildcard client_id in the message' do
855
+ it 'throws an exception' do
856
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
857
+ stop_reactor
858
+ end
859
+ end
860
+
861
+ context 'with an empty client_id in the message' do
862
+ it 'succeeds and publishes with an implicit client_id' do
863
+ channel.publish([name: 'event', client_id: nil]).tap do |deferrable|
864
+ deferrable.errback { raise 'Should have succeeded' }
865
+ end
866
+ channel.subscribe('event') do |message|
867
+ expect(message.client_id).to be_nil
868
+ EM.add_timer(0.5) { stop_reactor }
869
+ end
870
+ end
871
+ end
872
+ end
873
+ end
584
874
  end
585
875
 
586
876
  describe '#subscribe' do
@@ -881,7 +1171,7 @@ describe Ably::Realtime::Channel, :event_machine do
881
1171
 
882
1172
  context 'a :failed channel' do
883
1173
  let(:original_error) { RuntimeError.new }
884
- let(:client_options) { default_options.merge(log_level: :fatal) }
1174
+ let(:client_options) { default_options.merge(log_level: :fatal) }
885
1175
 
886
1176
  it 'remains in the :failed state and retains the error_reason' do
887
1177
  channel.attach do
@@ -904,6 +1194,8 @@ describe Ably::Realtime::Channel, :event_machine do
904
1194
  end
905
1195
 
906
1196
  context 'a channel ATTACH request when connection SUSPENDED' do
1197
+ let(:client_options) { default_options.merge(log_level: :fatal) }
1198
+
907
1199
  it 'raises an exception' do
908
1200
  client.connect do
909
1201
  client.connection.once(:suspended) do
@@ -53,7 +53,7 @@ describe Ably::Realtime::Client, :event_machine do
53
53
  connection.on(:connected) do
54
54
  expect(auth_params[:access_token]).to_not be_nil
55
55
  expect(auth_params[:key]).to be_nil
56
- expect(subject.auth.current_token_details).to be_nil
56
+ expect(subject.auth.current_token_details).to be_a(Ably::Models::TokenDetails)
57
57
  stop_reactor
58
58
  end
59
59
  end
@@ -113,6 +113,98 @@ describe Ably::Realtime::Client, :event_machine do
113
113
  stop_reactor
114
114
  end
115
115
  end
116
+
117
+ context 'when the returned token has a client_id' do
118
+ it "sets Auth#client_id to the new token's client_id immediately when connecting" do
119
+ subject.auth.authorise do
120
+ expect(subject.connection).to be_connecting
121
+ expect(subject.auth.client_id).to eql(client_id)
122
+ stop_reactor
123
+ end
124
+ end
125
+
126
+ it "sets Client#client_id to the new token's client_id immediately when connecting" do
127
+ subject.auth.authorise do
128
+ expect(subject.connection).to be_connecting
129
+ expect(subject.client_id).to eql(client_id)
130
+ stop_reactor
131
+ end
132
+ end
133
+ end
134
+
135
+ context 'with a wildcard client_id token' do
136
+ subject { auto_close Ably::Realtime::Client.new(client_options) }
137
+ let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id) }
138
+ let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
139
+ let(:auth_token_object) { rest_auth_client.auth.request_token(client_id: '*') }
140
+
141
+ context 'and an explicit client_id in ClientOptions' do
142
+ let(:client_id) { random_str }
143
+
144
+ it 'allows uses the explicit client_id in the connection' do
145
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
146
+ if protocol_message.action == :connected
147
+ expect(protocol_message.connection_details.client_id).to eql(client_id)
148
+ @valid_client_id = true
149
+ end
150
+ end
151
+ subject.connect do
152
+ EM.add_timer(0.5) { stop_reactor if @valid_client_id }
153
+ end
154
+ end
155
+ end
156
+
157
+ context 'and client_id omitted in ClientOptions' do
158
+ let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }) }
159
+
160
+ it 'uses the token provided clientId in the connection' do
161
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
162
+ if protocol_message.action == :connected
163
+ expect(protocol_message.connection_details.client_id).to eql('*')
164
+ @valid_client_id = true
165
+ end
166
+ end
167
+ subject.connect do
168
+ EM.add_timer(0.5) { stop_reactor if @valid_client_id }
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ context 'with an invalid wildcard "*" :client_id' do
176
+ it 'raises an exception' do
177
+ expect { Ably::Realtime::Client.new(client_options.merge(key: api_key, client_id: '*')) }.to raise_error ArgumentError
178
+ stop_reactor
179
+ end
180
+ end
181
+ end
182
+
183
+ context 'realtime connection settings' do
184
+ context 'defaults' do
185
+ specify 'disconnected_retry_timeout is 15s' do
186
+ expect(subject.connection.defaults[:disconnected_retry_timeout]).to eql(15)
187
+ stop_reactor
188
+ end
189
+
190
+ specify 'suspended_retry_timeout is 30s' do
191
+ expect(subject.connection.defaults[:suspended_retry_timeout]).to eql(30)
192
+ stop_reactor
193
+ end
194
+ end
195
+
196
+ context 'overriden in ClientOptions' do
197
+ let(:client_options) { default_options.merge(disconnected_retry_timeout: 1, suspended_retry_timeout: 2) }
198
+
199
+ specify 'disconnected_retry_timeout is updated' do
200
+ expect(subject.connection.defaults[:disconnected_retry_timeout]).to eql(1)
201
+ stop_reactor
202
+ end
203
+
204
+ specify 'suspended_retry_timeout is updated' do
205
+ expect(subject.connection.defaults[:suspended_retry_timeout]).to eql(2)
206
+ stop_reactor
207
+ end
116
208
  end
117
209
  end
118
210
  end