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.
- checksums.yaml +4 -4
- data/.editorconfig +14 -0
- data/.travis.yml +4 -4
- data/CHANGELOG.md +26 -3
- data/Rakefile +32 -0
- data/SPEC.md +920 -565
- data/ably.gemspec +9 -4
- data/lib/ably/auth.rb +28 -2
- data/lib/ably/exceptions.rb +8 -2
- data/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/ably/models/device_details.rb +87 -0
- data/lib/ably/models/device_push_details.rb +86 -0
- data/lib/ably/models/error_info.rb +23 -2
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/ably/models/protocol_message.rb +32 -2
- data/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/ably/modules/conversions.rb +1 -1
- data/lib/ably/modules/encodeable.rb +1 -1
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/model_common.rb +15 -2
- data/lib/ably/modules/state_machine.rb +1 -1
- data/lib/ably/realtime.rb +1 -0
- data/lib/ably/realtime/auth.rb +1 -1
- data/lib/ably/realtime/channel.rb +24 -102
- data/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/ably/realtime/client.rb +87 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +8 -5
- data/lib/ably/realtime/connection/connection_manager.rb +7 -7
- data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/ably/realtime/presence.rb +4 -4
- data/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/ably/realtime/push.rb +40 -0
- data/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/rest.rb +1 -0
- data/lib/ably/rest/channel.rb +33 -5
- data/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/ably/rest/client.rb +137 -28
- data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/ably/rest/presence.rb +1 -0
- data/lib/ably/rest/push.rb +42 -0
- data/lib/ably/rest/push/admin.rb +54 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/ably/version.rb +7 -2
- data/spec/acceptance/realtime/auth_spec.rb +6 -8
- data/spec/acceptance/realtime/channel_spec.rb +166 -51
- data/spec/acceptance/realtime/client_spec.rb +149 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +1 -1
- data/spec/acceptance/realtime/connection_spec.rb +4 -4
- data/spec/acceptance/realtime/message_spec.rb +19 -17
- data/spec/acceptance/realtime/presence_spec.rb +5 -5
- data/spec/acceptance/realtime/push_admin_spec.rb +696 -0
- data/spec/acceptance/realtime/push_spec.rb +27 -0
- data/spec/acceptance/rest/auth_spec.rb +4 -3
- data/spec/acceptance/rest/base_spec.rb +2 -2
- data/spec/acceptance/rest/client_spec.rb +129 -10
- data/spec/acceptance/rest/message_spec.rb +175 -4
- data/spec/acceptance/rest/push_admin_spec.rb +896 -0
- data/spec/acceptance/rest/push_spec.rb +25 -0
- data/spec/acceptance/rest/time_spec.rb +1 -1
- data/spec/run_parallel_tests +33 -0
- data/spec/unit/logger_spec.rb +10 -3
- data/spec/unit/models/device_details_spec.rb +102 -0
- data/spec/unit/models/device_push_details_spec.rb +101 -0
- data/spec/unit/models/error_info_spec.rb +51 -3
- data/spec/unit/models/message_spec.rb +17 -2
- data/spec/unit/models/presence_message_spec.rb +1 -1
- data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/spec/unit/realtime/client_spec.rb +12 -0
- data/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/spec/unit/rest/channel_spec.rb +8 -1
- data/spec/unit/rest/client_spec.rb +30 -0
- data/spec/unit/rest/push_channel_spec.rb +36 -0
- 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
|
-
|
762
|
-
auth.
|
763
|
-
|
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: :
|
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: :
|
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.
|
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
|
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
|
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
|
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
|
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
|