ably 1.0.7 → 1.1.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.travis.yml +4 -4
  4. data/CHANGELOG.md +26 -3
  5. data/Rakefile +32 -0
  6. data/SPEC.md +920 -565
  7. data/ably.gemspec +9 -4
  8. data/lib/ably/auth.rb +28 -2
  9. data/lib/ably/exceptions.rb +8 -2
  10. data/lib/ably/models/channel_state_change.rb +1 -1
  11. data/lib/ably/models/connection_state_change.rb +1 -1
  12. data/lib/ably/models/device_details.rb +87 -0
  13. data/lib/ably/models/device_push_details.rb +86 -0
  14. data/lib/ably/models/error_info.rb +23 -2
  15. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  16. data/lib/ably/models/protocol_message.rb +32 -2
  17. data/lib/ably/models/push_channel_subscription.rb +89 -0
  18. data/lib/ably/modules/conversions.rb +1 -1
  19. data/lib/ably/modules/encodeable.rb +1 -1
  20. data/lib/ably/modules/exception_codes.rb +128 -0
  21. data/lib/ably/modules/model_common.rb +15 -2
  22. data/lib/ably/modules/state_machine.rb +1 -1
  23. data/lib/ably/realtime.rb +1 -0
  24. data/lib/ably/realtime/auth.rb +1 -1
  25. data/lib/ably/realtime/channel.rb +24 -102
  26. data/lib/ably/realtime/channel/channel_manager.rb +2 -6
  27. data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  28. data/lib/ably/realtime/channel/publisher.rb +74 -0
  29. data/lib/ably/realtime/channel/push_channel.rb +62 -0
  30. data/lib/ably/realtime/client.rb +87 -0
  31. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  32. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  33. data/lib/ably/realtime/connection.rb +8 -5
  34. data/lib/ably/realtime/connection/connection_manager.rb +7 -7
  35. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  36. data/lib/ably/realtime/presence.rb +4 -4
  37. data/lib/ably/realtime/presence/members_map.rb +3 -3
  38. data/lib/ably/realtime/push.rb +40 -0
  39. data/lib/ably/realtime/push/admin.rb +61 -0
  40. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  41. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  42. data/lib/ably/rest.rb +1 -0
  43. data/lib/ably/rest/channel.rb +33 -5
  44. data/lib/ably/rest/channel/push_channel.rb +62 -0
  45. data/lib/ably/rest/client.rb +137 -28
  46. data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  47. data/lib/ably/rest/presence.rb +1 -0
  48. data/lib/ably/rest/push.rb +42 -0
  49. data/lib/ably/rest/push/admin.rb +54 -0
  50. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  51. data/lib/ably/rest/push/device_registrations.rb +103 -0
  52. data/lib/ably/version.rb +7 -2
  53. data/spec/acceptance/realtime/auth_spec.rb +6 -8
  54. data/spec/acceptance/realtime/channel_spec.rb +166 -51
  55. data/spec/acceptance/realtime/client_spec.rb +149 -0
  56. data/spec/acceptance/realtime/connection_failures_spec.rb +1 -1
  57. data/spec/acceptance/realtime/connection_spec.rb +4 -4
  58. data/spec/acceptance/realtime/message_spec.rb +19 -17
  59. data/spec/acceptance/realtime/presence_spec.rb +5 -5
  60. data/spec/acceptance/realtime/push_admin_spec.rb +696 -0
  61. data/spec/acceptance/realtime/push_spec.rb +27 -0
  62. data/spec/acceptance/rest/auth_spec.rb +4 -3
  63. data/spec/acceptance/rest/base_spec.rb +2 -2
  64. data/spec/acceptance/rest/client_spec.rb +129 -10
  65. data/spec/acceptance/rest/message_spec.rb +175 -4
  66. data/spec/acceptance/rest/push_admin_spec.rb +896 -0
  67. data/spec/acceptance/rest/push_spec.rb +25 -0
  68. data/spec/acceptance/rest/time_spec.rb +1 -1
  69. data/spec/run_parallel_tests +33 -0
  70. data/spec/unit/logger_spec.rb +10 -3
  71. data/spec/unit/models/device_details_spec.rb +102 -0
  72. data/spec/unit/models/device_push_details_spec.rb +101 -0
  73. data/spec/unit/models/error_info_spec.rb +51 -3
  74. data/spec/unit/models/message_spec.rb +17 -2
  75. data/spec/unit/models/presence_message_spec.rb +1 -1
  76. data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  77. data/spec/unit/realtime/client_spec.rb +12 -0
  78. data/spec/unit/realtime/push_channel_spec.rb +36 -0
  79. data/spec/unit/rest/channel_spec.rb +8 -1
  80. data/spec/unit/rest/client_spec.rb +30 -0
  81. data/spec/unit/rest/push_channel_spec.rb +36 -0
  82. metadata +71 -8
@@ -0,0 +1,103 @@
1
+ module Ably::Rest
2
+ class Push
3
+ # Manage device registrations for push notifications
4
+ class DeviceRegistrations
5
+ include Ably::Modules::Conversions
6
+
7
+ # @api private
8
+ attr_reader :client
9
+
10
+ # @api private
11
+ attr_reader :admin
12
+
13
+ def initialize(admin)
14
+ @admin = admin
15
+ @client = admin.client
16
+ end
17
+
18
+ # Get registered device by device ID
19
+ #
20
+ # @param [String, Ably::Models::DeviceDetails] device_id the device to retrieve
21
+ #
22
+ # @return [Ably::Models::DeviceDetails] Returns {Ably::Models::DeviceDetails} if a match is found else a {Ably::Exceptions::ResourceMissing} is raised
23
+ #
24
+ def get(device_id)
25
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
26
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
27
+
28
+ DeviceDetails(client.get("/push/deviceRegistrations/#{device_id}").body)
29
+ end
30
+
31
+ # List registered devices filtered by optional params
32
+ #
33
+ # @param [Hash] params the filter options for the list registered device request
34
+ # @option params [String] :client_id filter by devices registered to a client identifier. Cannot be used with +device_id+ param
35
+ # @option params [String] :device_id filter by unique device ID. Cannot be used with +client_id+ param
36
+ # @option params [Integer] :limit maximum number of devices to retrieve up to 1,000, defaults to 100
37
+ #
38
+ # @return [Ably::Models::PaginatedResult<Ably::Models::DeviceDetails>] Paginated list of matching {Ably::Models::DeviceDetails}
39
+ #
40
+ def list(params = {})
41
+ params = {} if params.nil?
42
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
43
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
44
+
45
+ params = params.clone
46
+
47
+ paginated_options = {
48
+ coerce_into: 'Ably::Models::DeviceDetails',
49
+ async_blocking_operations: params.delete(:async_blocking_operations),
50
+ }
51
+
52
+ response = client.get('/push/deviceRegistrations', IdiomaticRubyWrapper(params).as_json)
53
+
54
+ Ably::Models::PaginatedResult.new(response, '', client, paginated_options)
55
+ end
56
+
57
+ # Save and register device
58
+ #
59
+ # @param [Ably::Models::DeviceDetails, Hash] device the device details to save
60
+ #
61
+ # @return [void]
62
+ #
63
+ def save(device)
64
+ device_details = DeviceDetails(device)
65
+ raise ArgumentError, "Device ID is required yet is empty" if device_details.id.nil? || device_details == ''
66
+
67
+ client.put("/push/deviceRegistrations/#{device_details.id}", device_details.as_json)
68
+ end
69
+
70
+ # Remove device
71
+ #
72
+ # @param [String, Ably::Models::DeviceDetails] device_id the device to remove
73
+ #
74
+ # @return [void]
75
+ #
76
+ def remove(device_id)
77
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
78
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
79
+
80
+ client.delete("/push/deviceRegistrations/#{device_id}", {})
81
+ end
82
+
83
+ # Remove device matching where params
84
+ #
85
+ # @param [Hash] params the filter params for the remove request
86
+ # @option params [String] :client_id remove devices registered to a client identifier. Cannot be used with +device_id+ param
87
+ # @option params [String] :device_id remove device with this unique device ID. Cannot be used with +client_id+ param
88
+ #
89
+ # @return [void]
90
+ #
91
+ def remove_where(params = {})
92
+ filter = if params.kind_of?(Ably::Models::DeviceDetails)
93
+ { 'deviceId' => params.id }
94
+ else
95
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
96
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
97
+ IdiomaticRubyWrapper(params).as_json
98
+ end
99
+ client.delete("/push/deviceRegistrations", filter)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,6 +1,6 @@
1
1
  module Ably
2
- VERSION = '1.0.7'
3
- PROTOCOL_VERSION = '1.0'
2
+ VERSION = '1.1.0'
3
+ PROTOCOL_VERSION = '1.1'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
6
6
  # such as ruby-rest-[VERSION]
@@ -13,4 +13,9 @@ module Ably
13
13
  def self.lib_variant
14
14
  @lib_variant
15
15
  end
16
+
17
+ # @api private
18
+ def self.major_minor_version_numeric
19
+ VERSION.gsub(/\.\d+$/, '').to_f
20
+ end
16
21
  end
@@ -607,16 +607,17 @@ describe Ably::Realtime::Auth, :event_machine do
607
607
 
608
608
  context 'when auth fails' do
609
609
  let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
610
+ let!(:token_string) { client.rest_client.auth.request_token.token }
610
611
 
611
612
  it 'transitions the connection state to the FAILED state (#RSA15c, #RTC8a2, #RTC8a3)' do
612
613
  connection_failed = false
613
614
 
614
615
  client.connection.once(:connected) do
615
- client.auth.authorize(nil, auth_callback: lambda { |token_params| 'invalid.token:will.cause.failure' }).tap do |deferrable|
616
+ client.auth.authorize(nil, auth_callback: lambda { |token_params| "#{app_id}.invalid.token.will.cause.failure" }).tap do |deferrable|
616
617
  deferrable.errback do |error|
617
618
  EventMachine.add_timer(0.2) do
618
619
  expect(connection_failed).to eql(true)
619
- expect(error.message).to match(/Invalid accessToken/i)
620
+ expect(error.message).to match(/invalid.*accessToken/i)
620
621
  expect(error.code).to eql(40005)
621
622
  stop_reactor
622
623
  end
@@ -626,7 +627,7 @@ describe Ably::Realtime::Auth, :event_machine do
626
627
  end
627
628
 
628
629
  client.connection.once(:failed) do
629
- expect(client.connection.error_reason.message).to match(/Invalid accessToken/i)
630
+ expect(client.connection.error_reason.message).to match(/invalid.*accessToken/i)
630
631
  expect(client.connection.error_reason.code).to eql(40005)
631
632
  connection_failed = true
632
633
  end
@@ -749,11 +750,8 @@ describe Ably::Realtime::Auth, :event_machine do
749
750
  end
750
751
 
751
752
  context 'when received' do
752
- # Ably in all environments other than locla will send AUTH 30 seconds before expiry
753
- # We set the TTL to 33s and wait (3s window)
754
- # In local env, that window is 5 seconds instead of 30 seconds
755
- let(:local_offset) { ENV['ABLY_ENV'] == 'local' ? 25 : 0 }
756
- let(:client_options) { default_options.merge(use_token_auth: :true, default_token_params: { ttl: 33 - local_offset }) }
753
+ # Ably will send AUTH 30 seconds before expiry
754
+ let(:client_options) { default_options.merge(use_token_auth: :true, default_token_params: { ttl: 33 }) }
757
755
 
758
756
  it 'should immediately start a new authentication process (#RTN22)' do
759
757
  client.connection.once(:connected) do
@@ -13,6 +13,9 @@ describe Ably::Realtime::Channel, :event_machine do
13
13
  let(:channel) { client.channel(channel_name) }
14
14
  let(:messages) { [] }
15
15
 
16
+ let(:sub_client) { auto_close Ably::Realtime::Client.new(client_options) }
17
+ let(:sub_channel) { sub_client.channel(channel_name) }
18
+
16
19
  def disconnect_transport
17
20
  connection.transport.unbind
18
21
  end
@@ -726,7 +729,7 @@ describe Ably::Realtime::Channel, :event_machine do
726
729
  let(:name) { random_str }
727
730
  let(:data) { random_str }
728
731
 
729
- context 'when attached' do
732
+ context 'when channel is attached (#RTL6c1)' do
730
733
  it 'publishes messages' do
731
734
  channel.attach do
732
735
  3.times { channel.publish('event', payload) }
@@ -738,81 +741,173 @@ describe Ably::Realtime::Channel, :event_machine do
738
741
  end
739
742
  end
740
743
 
741
- context 'when not yet attached' do
742
- it 'publishes queued messages once attached' do
743
- 3.times { channel.publish('event', random_str) }
744
- channel.subscribe do |message|
745
- messages << message if message.name == 'event'
746
- stop_reactor if messages.count == 3
744
+ context 'when channel is not attached in state Initializing (#RTL6c1)' do
745
+ it 'publishes messages immediately and does not implicitly attach (#RTL6c1)' do
746
+ sub_channel.attach do
747
+ sub_channel.subscribe do |message|
748
+ messages << message if message.name == 'event'
749
+ if messages.count == 3
750
+ EventMachine.add_timer(1) do
751
+ expect(channel.state).to eq(:initialized)
752
+ stop_reactor
753
+ end
754
+ end
755
+ end
756
+ 3.times { channel.publish('event', random_str) }
747
757
  end
748
758
  end
759
+ end
749
760
 
750
- it 'publishes queued messages within a single protocol message' do
751
- 3.times { channel.publish('event', random_str) }
752
- channel.subscribe do |message|
753
- messages << message if message.name == 'event'
754
- next unless messages.length == 3
755
-
756
- # All 3 messages should be batched into a single Protocol Message by the client library
757
- # message.id = "{protocol_message.id}:{protocol_message_index}"
758
- # Check that all messages share the same protocol_message.id
759
- message_id = messages.map { |msg| msg.id.split(':')[0...-1].join(':') }
760
- expect(message_id.uniq.count).to eql(1)
761
-
762
- # Check that messages use index 0,1,2 in the ID
763
- message_indexes = messages.map { |msg| msg.id.split(':').last }
764
- expect(message_indexes).to include("0", "1", "2")
765
- stop_reactor
761
+ context 'when channel is Attaching (#RTL6c1)' do
762
+ it 'publishes messages immediately (#RTL6c1)' do
763
+ sub_channel.attach do
764
+ channel.once(:attaching) do
765
+ outgoing_message_count = 0
766
+ client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
767
+ if protocol_message.action == :message
768
+ raise "Expected channel state to be attaching when publishing messages, not #{channel.state}" unless channel.attaching?
769
+ outgoing_message_count += protocol_message.messages.count
770
+ end
771
+ end
772
+ sub_channel.subscribe do |message|
773
+ messages << message if message.name == 'event'
774
+ if messages.count == 3
775
+ expect(outgoing_message_count).to eql(3)
776
+ stop_reactor
777
+ end
778
+ end
779
+ 3.times { channel.publish('event', random_str) }
780
+ end
781
+ channel.attach
766
782
  end
767
783
  end
784
+ end
768
785
 
769
- context 'with :queue_messages client option set to false' do
770
- let(:client_options) { default_options.merge(queue_messages: false) }
771
-
772
- context 'and connection state initialized' do
773
- it 'fails the deferrable' do
774
- expect(client.connection).to be_initialized
775
- channel.publish('event').errback do |error|
776
- expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
777
- stop_reactor
786
+ context 'when channel is Detaching (#RTL6c1)' do
787
+ it 'publishes messages immediately (#RTL6c1)' do
788
+ sub_channel.attach do
789
+ channel.attach do
790
+ channel.once(:detaching) do
791
+ outgoing_message_count = 0
792
+ client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
793
+ if protocol_message.action == :message
794
+ raise "Expected channel state to be attaching when publishing messages, not #{channel.state}" unless channel.detaching?
795
+ outgoing_message_count += protocol_message.messages.count
796
+ end
797
+ end
798
+ sub_channel.subscribe do |message|
799
+ messages << message if message.name == 'event'
800
+ if messages.count == 3
801
+ expect(outgoing_message_count).to eql(3)
802
+ stop_reactor
803
+ end
804
+ end
805
+ 3.times { channel.publish('event', random_str) }
778
806
  end
807
+ channel.detach
779
808
  end
780
809
  end
810
+ end
811
+ end
781
812
 
782
- context 'and connection state connecting' do
783
- it 'fails the deferrable' do
784
- client.connect
785
- EventMachine.next_tick do
786
- expect(client.connection).to be_connecting
787
- channel.publish('event').errback do |error|
788
- expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
789
- stop_reactor
813
+ context 'when channel is Detached (#RTL6c1)' do
814
+ it 'publishes messages immediately (#RTL6c1)' do
815
+ sub_channel.attach do
816
+ channel.attach
817
+ channel.once(:attached) do
818
+ channel.once(:detached) do
819
+ outgoing_message_count = 0
820
+ client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
821
+ if protocol_message.action == :message
822
+ raise "Expected channel state to be attaching when publishing messages, not #{channel.state}" unless channel.detached?
823
+ outgoing_message_count += protocol_message.messages.count
824
+ end
825
+ end
826
+ sub_channel.subscribe do |message|
827
+ messages << message if message.name == 'event'
828
+ if messages.count == 3
829
+ expect(outgoing_message_count).to eql(3)
830
+ stop_reactor
831
+ end
790
832
  end
833
+ 3.times { channel.publish('event', random_str) }
834
+ end
835
+ channel.detach
836
+ end
837
+ end
838
+ end
839
+ end
840
+
841
+ context 'with :queue_messages client option set to false (#RTL6c4)' do
842
+ let(:client_options) { default_options.merge(queue_messages: false) }
843
+
844
+ context 'and connection state connected (#RTL6c4)' do
845
+ it 'publishes the message' do
846
+ client.connection.once(:connected) do
847
+ channel.publish('event')
848
+ stop_reactor
849
+ end
850
+ end
851
+ end
852
+
853
+ context 'and connection state initialized (#RTL6c4)' do
854
+ it 'fails the deferrable' do
855
+ expect(client.connection).to be_initialized
856
+ channel.publish('event').errback do |error|
857
+ expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
858
+ stop_reactor
859
+ end
860
+ end
861
+ end
862
+
863
+ context 'and connection state connecting (#RTL6c4)' do
864
+ it 'fails the deferrable' do
865
+ client.connect
866
+ EventMachine.next_tick do
867
+ expect(client.connection).to be_connecting
868
+ channel.publish('event').errback do |error|
869
+ expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
870
+ stop_reactor
791
871
  end
792
872
  end
793
873
  end
874
+ end
794
875
 
795
- context 'and connection state disconnected' do
876
+ [:disconnected, :suspended, :closing, :closed].each do |invalid_connection_state|
877
+ context "and connection state #{invalid_connection_state} (#RTL6c4)" do
796
878
  let(:client_options) { default_options.merge(queue_messages: false) }
797
879
  it 'fails the deferrable' do
798
880
  client.connection.once(:connected) do
799
- client.connection.once(:disconnected) do
800
- expect(client.connection).to be_disconnected
881
+ client.connection.once(invalid_connection_state) do
882
+ expect(client.connection.state).to eq(invalid_connection_state)
801
883
  channel.publish('event').errback do |error|
802
884
  expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
803
885
  stop_reactor
804
886
  end
805
887
  end
806
- client.connection.transition_state_machine :disconnected
888
+ if invalid_connection_state == :closed
889
+ connection.close
890
+ else
891
+ client.connection.transition_state_machine invalid_connection_state
892
+ end
807
893
  end
808
894
  end
809
895
  end
896
+ end
810
897
 
811
- context 'and connection state connected' do
812
- it 'publishes the message' do
813
- client.connection.once(:connected) do
814
- channel.publish('event')
815
- stop_reactor
898
+ context 'and the channel state is failed (#RTL6c4)' do
899
+ let(:client_options) { default_options.merge(queue_messages: false) }
900
+ it 'fails the deferrable' do
901
+ client.connection.once(:connected) do
902
+ channel.attach
903
+ channel.once(:attached) do
904
+ channel.once(:failed) do
905
+ channel.publish('event').errback do |error|
906
+ expect(error).to be_a(Ably::Exceptions::ChannelInactive)
907
+ stop_reactor
908
+ end
909
+ end
910
+ channel.transition_state_machine(:failed)
816
911
  end
817
912
  end
818
913
  end
@@ -1074,6 +1169,21 @@ describe Ably::Realtime::Channel, :event_machine do
1074
1169
  end
1075
1170
  end
1076
1171
 
1172
+ context 'with more than allowed messages in a single publish' do
1173
+ let(:channel_name) { random_str }
1174
+
1175
+ it 'rejects the publish' do
1176
+ messages = (Ably::Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE + 1).times.map do
1177
+ { name: 'foo' }
1178
+ end
1179
+
1180
+ channel.publish(messages).errback do |error|
1181
+ expect(error).to be_kind_of(Ably::Exceptions::InvalidRequest)
1182
+ stop_reactor
1183
+ end
1184
+ end
1185
+ end
1186
+
1077
1187
  context 'identified clients' do
1078
1188
  context 'when authenticated with a wildcard client_id' do
1079
1189
  let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*') }
@@ -1953,8 +2063,12 @@ describe Ably::Realtime::Channel, :event_machine do
1953
2063
  end
1954
2064
 
1955
2065
  context 'moves to' do
1956
- %w(suspended detached failed).each do |channel_state|
2066
+ %w(suspended failed).each do |channel_state|
1957
2067
  context(channel_state) do
2068
+ let(:client) do
2069
+ auto_close Ably::Realtime::Client.new(default_options.merge(log_level: :error))
2070
+ end
2071
+
1958
2072
  specify 'all queued messages fail with NACK (#RTL11)' do
1959
2073
  channel.attach do
1960
2074
  # Move to disconnected
@@ -1981,7 +2095,8 @@ describe Ably::Realtime::Channel, :event_machine do
1981
2095
  specify 'all published messages awaiting an ACK do nothing (#RTL11a)' do
1982
2096
  connection_been_disconnected = false
1983
2097
 
1984
- channel.attach do
2098
+ channel.attach
2099
+ channel.once(:attached) do
1985
2100
  deferrable = channel.publish("foo")
1986
2101
  deferrable.errback do |error|
1987
2102
  fail "Message publish should not fail"
@@ -12,6 +12,7 @@ describe Ably::Realtime::Client, :event_machine do
12
12
  let(:auth_params) { subject.auth.auth_params_sync }
13
13
 
14
14
  subject { auto_close Ably::Realtime::Client.new(client_options) }
15
+ let(:sub_client) { auto_close Ably::Realtime::Client.new(client_options) }
15
16
 
16
17
  context 'initialization' do
17
18
  context 'basic auth' do
@@ -24,6 +25,22 @@ describe Ably::Realtime::Client, :event_machine do
24
25
  end
25
26
  end
26
27
 
28
+ context 'with an invalid API key' do
29
+ let(:custom_logger_object) { TestLogger.new }
30
+ let(:client) { Ably::Realtime::Client.new(client_options.merge(key: 'app.key:secret', logger: custom_logger_object)) }
31
+
32
+ it 'logs an entry with a help href url matching the code #TI5' do
33
+ client.connect
34
+ client.connection.once(:failed) do
35
+ expect(custom_logger_object.logs.find do |severity, message|
36
+ next unless %w(fatal error).include?(severity.to_s)
37
+ message.match(%r{https://help.ably.io/error/40400})
38
+ end).to_not be_nil
39
+ stop_reactor
40
+ end
41
+ end
42
+ end
43
+
27
44
  context ':tls option' do
28
45
  context 'set to false to force a plain-text connection' do
29
46
  let(:client_options) { default_options.merge(tls: false, log_level: :none) }
@@ -281,5 +298,137 @@ describe Ably::Realtime::Client, :event_machine do
281
298
  end
282
299
  end
283
300
  end
301
+
302
+ context '#publish (#TBC)' do
303
+ let(:channel_name) { random_str }
304
+ let(:channel) { subject.channel(channel_name) }
305
+ let(:sub_channel) { sub_client.channel(channel_name) }
306
+ let(:event_name) { random_str }
307
+ let(:data) { random_str }
308
+ let(:extras) { { 'push' => { 'notification' => { 'title' => 'Testing' } } } }
309
+ let(:message) { Ably::Models::Message.new(name: event_name, data: data) }
310
+
311
+ specify 'publishing a message implicity connects and publishes the message successfully on the provided channel' do
312
+ sub_channel.attach do
313
+ sub_channel.subscribe do |msg|
314
+ expect(msg.name).to eql(event_name)
315
+ expect(msg.data).to eql(data)
316
+ stop_reactor
317
+ end
318
+ end
319
+ subject.publish channel_name, event_name, data
320
+ end
321
+
322
+ specify 'publishing does not result in a channel being created' do
323
+ subject.publish channel_name, event_name, data
324
+ subject.channels.fetch(channel_name) do
325
+ # Block called if channel does not exist
326
+ EventMachine.add_timer(1) do
327
+ subject.channels.fetch(channel_name) do
328
+ # Block called if channel does not exist
329
+ stop_reactor
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ context 'with extras' do
336
+ let(:channel_name) { "pushenabled:#{random_str}" }
337
+
338
+ specify 'publishing supports extras' do
339
+ sub_channel.attach do
340
+ sub_channel.subscribe do |msg|
341
+ expect(msg.extras).to eql(extras)
342
+ stop_reactor
343
+ end
344
+ end
345
+ subject.publish channel_name, event_name, {}, extras: extras
346
+ end
347
+ end
348
+
349
+ specify 'publishing supports an array of Message objects' do
350
+ sub_channel.attach do
351
+ sub_channel.subscribe do |msg|
352
+ expect(msg.name).to eql(event_name)
353
+ expect(msg.data).to eql(data)
354
+ stop_reactor
355
+ end
356
+ end
357
+ subject.publish channel_name, [message]
358
+ end
359
+
360
+ specify 'publishing supports an array of Hash objects' do
361
+ sub_channel.attach do
362
+ sub_channel.subscribe do |msg|
363
+ expect(msg.name).to eql(event_name)
364
+ expect(msg.data).to eql(data)
365
+ stop_reactor
366
+ end
367
+ end
368
+ subject.publish channel_name, [name: event_name, data: data]
369
+ end
370
+
371
+ specify 'publishing on a closed connection fails' do
372
+ subject.connection.once(:connected) do
373
+ subject.connection.once(:closed) do
374
+ subject.publish(channel_name, name: event_name).errback do |error|
375
+ expect(error).to be_kind_of(Ably::Exceptions::MessageQueueingDisabled)
376
+ stop_reactor
377
+ end
378
+ end
379
+ connection.close
380
+ end
381
+ end
382
+
383
+ context 'queue_messages ClientOption' do
384
+ context 'when true' do
385
+ subject { auto_close Ably::Realtime::Client.new(client_options.merge(auto_connect: false)) }
386
+
387
+ it 'will queue messages whilst connecting and publish once connected' do
388
+ sub_channel.attach do
389
+ sub_channel.subscribe do |msg|
390
+ expect(msg.name).to eql(event_name)
391
+ stop_reactor
392
+ end
393
+ subject.connection.once(:connecting) do
394
+ subject.publish channel_name, event_name
395
+ end
396
+ subject.connection.connect
397
+ end
398
+ end
399
+ end
400
+
401
+ context 'when false' do
402
+ subject { auto_close Ably::Realtime::Client.new(client_options.merge(auto_connect: false, queue_messages: false)) }
403
+
404
+ it 'will reject messages on an initializing connection' do
405
+ sub_channel.attach do
406
+ subject.connection.once(:connecting) do
407
+ subject.publish(channel_name, event_name).errback do |error|
408
+ expect(error).to be_kind_of(Ably::Exceptions::MessageQueueingDisabled)
409
+ stop_reactor
410
+ end
411
+ end
412
+ subject.connection.connect
413
+ end
414
+ end
415
+ end
416
+ end
417
+
418
+ context 'with more than allowed messages in a single publish' do
419
+ let(:channel_name) { random_str }
420
+
421
+ it 'rejects the publish' do
422
+ messages = (Ably::Realtime::Connection::MAX_PROTOCOL_MESSAGE_BATCH_SIZE + 1).times.map do
423
+ { name: 'foo' }
424
+ end
425
+
426
+ subject.publish(channel_name, messages).errback do |error|
427
+ expect(error).to be_kind_of(Ably::Exceptions::InvalidRequest)
428
+ stop_reactor
429
+ end
430
+ end
431
+ end
432
+ end
284
433
  end
285
434
  end