ably 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
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