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,27 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Realtime::Push, :event_machine do
5
+ vary_by_protocol do
6
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
7
+ let(:client_options) { default_options }
8
+ let(:client) do
9
+ Ably::Realtime::Client.new(client_options)
10
+ end
11
+ subject { client.push }
12
+
13
+ describe '#activate' do
14
+ it 'raises an unsupported exception' do
15
+ expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
16
+ stop_reactor
17
+ end
18
+ end
19
+
20
+ describe '#deactivate' do
21
+ it 'raises an unsupported exception' do
22
+ expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
23
+ stop_reactor
24
+ end
25
+ end
26
+ end
27
+ end
@@ -758,9 +758,10 @@ describe Ably::Auth do
758
758
  end
759
759
 
760
760
  it 'updates the persisted auth options that are then used for subsequent authorize requests' do
761
- expect(auth.options[:authUrl]).to be_nil
762
- auth.authorize({}, authUrl: 'http://foo.com')
763
- expect(auth.options[:authUrl]).to eql('http://foo.com')
761
+ auth_url = "https://echo.ably.io/?type=text&body=#{auth.request_token.token}"
762
+ expect(auth.options[:auth_url]).to be_nil
763
+ auth.authorize({}, auth_url: auth_url)
764
+ expect(auth.options[:auth_url]).to eql(auth_url)
764
765
  end
765
766
 
766
767
  context 'with a lambda for the :auth_callback option' do
@@ -7,7 +7,7 @@ describe Ably::Rest do
7
7
 
8
8
  let(:client_options) { {} }
9
9
  let(:client) do
10
- Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret'))
10
+ Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret', log_retries_as_info: true))
11
11
  end
12
12
 
13
13
  let(:now) { Time.now - 1000 }
@@ -67,7 +67,7 @@ describe Ably::Rest do
67
67
 
68
68
  vary_by_protocol do
69
69
  let(:client) do
70
- Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol)
70
+ Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true)
71
71
  end
72
72
 
73
73
  describe 'failed requests' do
@@ -4,7 +4,7 @@ require 'webrick'
4
4
 
5
5
  describe Ably::Rest::Client do
6
6
  vary_by_protocol do
7
- let(:default_options) { { environment: environment, protocol: protocol } }
7
+ let(:default_options) { { environment: environment, protocol: protocol, log_retries_as_info: true } }
8
8
  let(:client_options) { default_options }
9
9
 
10
10
  let(:client) { Ably::Rest::Client.new(client_options) }
@@ -27,6 +27,19 @@ describe Ably::Rest::Client do
27
27
  end
28
28
  end
29
29
 
30
+ context 'with an invalid API key' do
31
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: 'app.key:secret', log_level: :fatal)) }
32
+
33
+ it 'logs an entry with a help href url matching the code #TI5' do
34
+ begin
35
+ client.channels.get('foo').publish('test')
36
+ raise 'Expected Ably::Exceptions::ResourceMissing'
37
+ rescue Ably::Exceptions::ResourceMissing => err
38
+ expect err.to_s.match(%r{https://help.ably.io/error/40400})
39
+ end
40
+ end
41
+ end
42
+
30
43
  context 'with an explicit string :token' do
31
44
  let(:client) { Ably::Rest::Client.new(client_options.merge(token: random_str)) }
32
45
 
@@ -710,6 +723,109 @@ describe Ably::Rest::Client do
710
723
  expect(@fallback_request_count).to eql(2)
711
724
  end
712
725
  end
726
+
727
+ context 'to fail the primary host, allow a fallback to succeed, then later trigger a fallback to the primary host (#RSC15f)' do
728
+ before do
729
+ @request_count = 0
730
+ @primary_host_request_count = 0
731
+ @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
732
+ @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
733
+ @request_count += 1
734
+ if req.header["host"].first.include?(primary_host)
735
+ @primary_host_request_count += 1
736
+ # Fail all requests to the primary host so that a fallback is used
737
+ # Except request 6 which should suceed and clear the fallback host preference
738
+ if @request_count == 6
739
+ res.status = 200
740
+ res['Content-Type'] = 'application/json'
741
+ res.body = '{}'
742
+ else
743
+ res.status = 500
744
+ end
745
+ else
746
+ # Fail the second request (first failed fallback of first request)
747
+ # Fail the third request on the previously succeeded fallback host to trigger an attempt on the primary host
748
+ if [2, 5].include?(@request_count)
749
+ res.status = 500
750
+ else
751
+ res.status = 200
752
+ res['Content-Type'] = 'application/json'
753
+ res.body = '{}'
754
+ end
755
+ end
756
+ end
757
+
758
+ Thread.new do
759
+ @web_server.start
760
+ end
761
+ end
762
+
763
+ let(:client_options) do
764
+ default_options.merge(
765
+ rest_host: primary_host,
766
+ fallback_hosts: fallbacks,
767
+ token: 'fake.token',
768
+ port: port,
769
+ tls: false,
770
+ log_level: :error
771
+ ).merge(additional_client_options)
772
+ end
773
+
774
+ let (:additional_client_options) { {} }
775
+
776
+ it 'succeeds and remembers fallback host preferences across requests' do
777
+ # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
778
+ client.channel(channel_name).publish('event', 'data')
779
+ expect(@request_count).to eql(3)
780
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
781
+ successfull_fallback = client.using_preferred_fallback_host?
782
+ expect(@primary_host_request_count).to eql(1)
783
+
784
+ # Send another request, which should go straight to the fallback as it succeeded previously
785
+ client.channel(channel_name).publish('event', 'data')
786
+ expect(@request_count).to eql(4)
787
+ expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
788
+ expect(@primary_host_request_count).to eql(1)
789
+
790
+ # A subsequent request should fail to the fallback, go the primary host and succeed
791
+ client.channel(channel_name).publish('event', 'data')
792
+ expect(@request_count).to eql(6)
793
+ expect(client.using_preferred_fallback_host?).to be_falsey
794
+ expect(@primary_host_request_count).to eql(2)
795
+
796
+ # A subsequent request will fail on the primary endpoint, and we expect the fallback to be used again
797
+ client.channel(channel_name).publish('event', 'data')
798
+ expect(@request_count).to eql(8)
799
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
800
+ successfull_fallback = client.using_preferred_fallback_host?
801
+ expect(@primary_host_request_count).to eql(3)
802
+
803
+ # Send another request, which should go straight to the fallback as it succeeded previously
804
+ client.channel(channel_name).publish('event', 'data')
805
+ expect(@request_count).to eql(9)
806
+ expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
807
+ expect(@primary_host_request_count).to eql(3)
808
+ end
809
+
810
+ context 'with custom :fallback_retry_timeout' do
811
+ let (:additional_client_options) { { fallback_retry_timeout: 5 } }
812
+
813
+ it 'stops using the preferred fallback after this time' do
814
+ # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
815
+ client.channel(channel_name).publish('event', 'data')
816
+ expect(@request_count).to eql(3)
817
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
818
+ expect(@primary_host_request_count).to eql(1)
819
+
820
+ # Wait for the preferred fallback cache to expire
821
+ sleep 5
822
+
823
+ # Send another request, which should go straight to the primary host again as fallback host is expired
824
+ client.channel(channel_name).publish('event', 'data')
825
+ expect(@primary_host_request_count).to eql(2)
826
+ end
827
+ end
828
+ end
713
829
  end
714
830
  end
715
831
 
@@ -724,7 +840,8 @@ describe Ably::Rest::Client do
724
840
  environment: env,
725
841
  key: api_key,
726
842
  http_max_retry_duration: max_retry_duration,
727
- http_max_retry_count: max_retry_count
843
+ http_max_retry_count: max_retry_count,
844
+ log_level: :fatal,
728
845
  )
729
846
  end
730
847
 
@@ -751,7 +868,7 @@ describe Ably::Rest::Client do
751
868
  end
752
869
 
753
870
  let(:client_options) {
754
- production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
871
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
755
872
  }
756
873
 
757
874
  it 'attempts the fallback hosts as this is not an authentication failure' do
@@ -764,7 +881,7 @@ describe Ably::Rest::Client do
764
881
 
765
882
  context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
766
883
  let(:client_options) {
767
- production_options.merge(fallback_hosts: [])
884
+ production_options.merge(fallback_hosts: [], log_level: :fatal)
768
885
  }
769
886
 
770
887
  it 'does not attempt the fallback hosts as this is an authentication failure' do
@@ -789,7 +906,7 @@ describe Ably::Rest::Client do
789
906
  end
790
907
 
791
908
  let(:client_options) {
792
- production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
909
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
793
910
  }
794
911
 
795
912
  it 'attempts the default fallback hosts as this is an authentication failure' do
@@ -966,7 +1083,7 @@ describe Ably::Rest::Client do
966
1083
  it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
967
1084
  client.channels.get('foo').publish("event")
968
1085
  expect(publish_message_stub).to have_been_requested
969
- expect(Ably::PROTOCOL_VERSION).to eql('1.0')
1086
+ expect(Ably::PROTOCOL_VERSION).to eql('1.1')
970
1087
  end
971
1088
  end
972
1089
  end
@@ -1084,7 +1201,7 @@ describe Ably::Rest::Client do
1084
1201
  end
1085
1202
 
1086
1203
  context 'option add_request_ids: true and specified fallback hosts', :webmock do
1087
- let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error } }
1204
+ let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error, log_retries_as_info: true } }
1088
1205
  let(:requests) { [] }
1089
1206
 
1090
1207
  before do
@@ -1140,7 +1257,7 @@ describe Ably::Rest::Client do
1140
1257
 
1141
1258
  context 'failed request logging', :prevent_log_stubbing do
1142
1259
  let(:custom_logger) { TestLogger.new }
1143
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger) }
1260
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger, log_retries_as_info: false) }
1144
1261
 
1145
1262
  it 'is absent when requests do not fail' do
1146
1263
  client.time
@@ -1153,7 +1270,8 @@ describe Ably::Rest::Client do
1153
1270
  rest_host: 'non.existent.domain.local',
1154
1271
  fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')],
1155
1272
  key: api_key,
1156
- logger: custom_logger)
1273
+ logger: custom_logger,
1274
+ log_retries_as_info: false)
1157
1275
  end
1158
1276
 
1159
1277
  it 'is present with success message when requests do not actually fail' do
@@ -1169,7 +1287,8 @@ describe Ably::Rest::Client do
1169
1287
  rest_host: 'non.existent.domain.local',
1170
1288
  fallback_hosts: ['non2.existent.domain.local'],
1171
1289
  key: api_key,
1172
- logger: custom_logger)
1290
+ logger: custom_logger,
1291
+ log_retries_as_info: false)
1173
1292
  end
1174
1293
 
1175
1294
  it 'is present when all requests fail' do
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
+ require 'base64'
3
4
  require 'securerandom'
4
5
 
5
6
  describe Ably::Rest::Channel, 'messages' do
@@ -91,11 +92,181 @@ describe Ably::Rest::Channel, 'messages' do
91
92
  end
92
93
  end
93
94
 
95
+ context 'idempotency (#RSL1k)' do
96
+ let(:id) { random_str }
97
+ let(:name) { 'event' }
98
+ let(:data) { random_str }
99
+
100
+ context 'when ID is not included (#RSL1k2)' do
101
+ context 'with Message object' do
102
+ let(:message) { Ably::Models::Message.new(data: data) }
103
+
104
+ it 'publishes the same message three times' do
105
+ 3.times { channel.publish [message] }
106
+ expect(channel.history.items.length).to eql(3)
107
+ end
108
+ end
109
+
110
+ context 'with #publish arguments only' do
111
+ it 'publishes the same message three times' do
112
+ 3.times { channel.publish 'event', data }
113
+ expect(channel.history.items.length).to eql(3)
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'when ID is included (#RSL1k2, #RSL1k5)' do
119
+ context 'with Message object' do
120
+ let(:message) { Ably::Models::Message.new(id: id, data: data) }
121
+
122
+ specify 'three REST publishes result in only one message being published' do
123
+ pending 'idempotency rolled out to global cluster'
124
+
125
+ 3.times { channel.publish [message] }
126
+ expect(channel.history.items.length).to eql(1)
127
+ expect(channel.history.items[0].id).to eql(id)
128
+ end
129
+ end
130
+
131
+ context 'with #publish arguments only' do
132
+ it 'three REST publishes result in only one message being published' do
133
+ pending 'idempotency rolled out to global cluster'
134
+
135
+ 3.times { channel.publish 'event', data, id: id }
136
+ expect(channel.history.items.length).to eql(1)
137
+ end
138
+ end
139
+
140
+ specify 'the ID provided is used for the published messages' do
141
+ channel.publish 'event', data, id: id
142
+ expect(channel.history.items[0].id).to eql(id)
143
+ end
144
+
145
+ specify 'for multiple messages in one publish operation (#RSL1k3)' do
146
+ pending 'idempotency rolled out to global cluster'
147
+
148
+ message_arr = 3.times.map { Ably::Models::Message.new(id: id, data: data) }
149
+ expect { channel.publish message_arr }.to raise_error do |error|
150
+ expect(error.code).to eql(40031) # Invalid publish request (invalid client-specified id), see https://github.com/ably/ably-common/pull/30
151
+ end
152
+ end
153
+
154
+ specify 'for multiple messages in one publish operation with IDs following the required format described in RSL1k1 (#RSL1k3)' do
155
+ pending 'idempotency rolled out to global cluster'
156
+
157
+ message_arr = 3.times.map { |index| Ably::Models::Message.new(id: "#{id}:#{index}", data: data) }
158
+ channel.publish message_arr
159
+ expect(channel.history.items[0].id).to eql("{id}:0")
160
+ expect(channel.history.items[2].id).to eql("{id}:2")
161
+ expect(channel.history.items.length).to eql(3)
162
+ end
163
+ end
164
+
165
+ specify 'idempotent publishing is disabled by default with 1.1 (#TO3n)' do
166
+ client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
167
+ expect(client.idempotent_rest_publishing).to be_falsey
168
+ end
169
+
170
+ specify 'idempotent publishing is enabled by default with 1.2 (#TO3n)' do
171
+ stub_const 'Ably::VERSION', '1.2.0'
172
+ client = Ably::Rest::Client.new(key: api_key, protocol: protocol)
173
+ expect(client.idempotent_rest_publishing).to be_truthy
174
+ end
175
+
176
+ context 'when idempotent publishing is enabled in the client library ClientOptions (#TO3n)' do
177
+ let(:client_options) { default_client_options.merge(idempotent_rest_publishing: true, log_level: :error) }
178
+
179
+ context 'when there is a network failure triggering an automatic retry (#RSL1k4)' do
180
+ def mock_for_two_publish_failures
181
+ @failed_http_posts = 0
182
+ allow(client).to receive(:can_fallback_to_alternate_ably_host?).and_return(true)
183
+ allow_any_instance_of(Faraday::Connection).to receive(:post) do |*args|
184
+ @failed_http_posts += 1
185
+ if @failed_http_posts == 2
186
+ # Ensure the 3rd requests operates as normal
187
+ allow_any_instance_of(Faraday::Connection).to receive(:post).and_call_original
188
+ end
189
+ raise Faraday::ClientError.new('Fake client error')
190
+ end
191
+ end
192
+
193
+ context 'with Message object' do
194
+ let(:message) { Ably::Models::Message.new(data: data) }
195
+ before { mock_for_two_publish_failures }
196
+
197
+ specify 'two REST publish retries result in only one message being published' do
198
+ pending 'idempotency rolled out to global cluster'
199
+
200
+ channel.publish [message]
201
+ expect(channel.history.items.length).to eql(1)
202
+ expect(@failed_http_posts).to eql(2)
203
+ end
204
+ end
205
+
206
+ context 'with #publish arguments only' do
207
+ before { mock_for_two_publish_failures }
208
+
209
+ specify 'two REST publish retries result in only one message being published' do
210
+ pending 'idempotency rolled out to global cluster'
211
+
212
+ channel.publish 'event', data
213
+ expect(channel.history.items.length).to eql(1)
214
+ expect(@failed_http_posts).to eql(2)
215
+ end
216
+ end
217
+
218
+ context 'with explicitly provided message ID' do
219
+ let(:id) { random_str }
220
+
221
+ before { mock_for_two_publish_failures }
222
+
223
+ specify 'two REST publish retries result in only one message being published' do
224
+ pending 'idempotency rolled out to global cluster'
225
+
226
+ channel.publish 'event', data, id: id
227
+ expect(channel.history.items.length).to eql(1)
228
+ expect(channel.history.items[0].id).to eql(id)
229
+ expect(@failed_http_posts).to eql(2)
230
+ end
231
+ end
232
+
233
+ specify 'for multiple messages in one publish operation' do
234
+ pending 'idempotency rolled out to global cluster'
235
+
236
+ message_arr = 3.times.map { Ably::Models::Message.new(data: data) }
237
+ 3.times { channel.publish message_arr }
238
+ expect(channel.history.items.length).to eql(message_arr.length)
239
+ end
240
+ end
241
+
242
+ specify 'the ID is populated with a random ID and serial 0 from this lib (#RSL1k1)' do
243
+ channel.publish 'event'
244
+ expect(channel.history.items[0].id).to match(/^[A-Za-z0-9\+\/]+:0$/)
245
+ base_64_id = channel.history.items[0].id.split(':')[0]
246
+ expect(Base64.decode64(base_64_id).length).to eql(9)
247
+ end
248
+
249
+ context 'when publishing a batch of messages' do
250
+ specify 'the ID is populated with a single random ID and sequence of serials from this lib (#RSL1k1)' do
251
+ pending 'idempotency rolled out to global cluster'
252
+
253
+ message = { name: 'event' }
254
+ channel.publish [message, message, message]
255
+ expect(channel.history.items[0].length).to eql(3)
256
+ expect(channel.history.items[0].id).to match(/^[A-Za-z0-9\+\/]+:0$/)
257
+ expect(channel.history.items[2].id).to match(/^[A-Za-z0-9\+\/]+:2$/)
258
+ base_64_id = channel.history.items[0].id.split(':')[0]
259
+ expect(Base64.decode64(base_64_id).length).to eql(9)
260
+ end
261
+ end
262
+ end
263
+ end
264
+
94
265
  context 'with unsupported data payload content type' do
95
266
  context 'Integer' do
96
267
  let(:data) { 1 }
97
268
 
98
- it 'is raises an UnsupportedDataType 40011 exception' do
269
+ it 'is raises an UnsupportedDataType 40013 exception' do
99
270
  expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType)
100
271
  end
101
272
  end
@@ -103,7 +274,7 @@ describe Ably::Rest::Channel, 'messages' do
103
274
  context 'Float' do
104
275
  let(:data) { 1.1 }
105
276
 
106
- it 'is raises an UnsupportedDataType 40011 exception' do
277
+ it 'is raises an UnsupportedDataType 40013 exception' do
107
278
  expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType)
108
279
  end
109
280
  end
@@ -111,7 +282,7 @@ describe Ably::Rest::Channel, 'messages' do
111
282
  context 'Boolean' do
112
283
  let(:data) { true }
113
284
 
114
- it 'is raises an UnsupportedDataType 40011 exception' do
285
+ it 'is raises an UnsupportedDataType 40013 exception' do
115
286
  expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType)
116
287
  end
117
288
  end
@@ -119,7 +290,7 @@ describe Ably::Rest::Channel, 'messages' do
119
290
  context 'False' do
120
291
  let(:data) { false }
121
292
 
122
- it 'is raises an UnsupportedDataType 40011 exception' do
293
+ it 'is raises an UnsupportedDataType 40013 exception' do
123
294
  expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType)
124
295
  end
125
296
  end