spec/acceptance/rest/message_spec.rb in ably-1.0.7 vs spec/acceptance/rest/message_spec.rb in ably-1.1.0

- old
+ new

@@ -1,7 +1,8 @@ # encoding: utf-8 require 'spec_helper' +require 'base64' require 'securerandom' describe Ably::Rest::Channel, 'messages' do include Ably::Modules::Conversions @@ -89,38 +90,208 @@ expect(channel.history.items.first.extras).to be_nil end end end + context 'idempotency (#RSL1k)' do + let(:id) { random_str } + let(:name) { 'event' } + let(:data) { random_str } + + context 'when ID is not included (#RSL1k2)' do + context 'with Message object' do + let(:message) { Ably::Models::Message.new(data: data) } + + it 'publishes the same message three times' do + 3.times { channel.publish [message] } + expect(channel.history.items.length).to eql(3) + end + end + + context 'with #publish arguments only' do + it 'publishes the same message three times' do + 3.times { channel.publish 'event', data } + expect(channel.history.items.length).to eql(3) + end + end + end + + context 'when ID is included (#RSL1k2, #RSL1k5)' do + context 'with Message object' do + let(:message) { Ably::Models::Message.new(id: id, data: data) } + + specify 'three REST publishes result in only one message being published' do + pending 'idempotency rolled out to global cluster' + + 3.times { channel.publish [message] } + expect(channel.history.items.length).to eql(1) + expect(channel.history.items[0].id).to eql(id) + end + end + + context 'with #publish arguments only' do + it 'three REST publishes result in only one message being published' do + pending 'idempotency rolled out to global cluster' + + 3.times { channel.publish 'event', data, id: id } + expect(channel.history.items.length).to eql(1) + end + end + + specify 'the ID provided is used for the published messages' do + channel.publish 'event', data, id: id + expect(channel.history.items[0].id).to eql(id) + end + + specify 'for multiple messages in one publish operation (#RSL1k3)' do + pending 'idempotency rolled out to global cluster' + + message_arr = 3.times.map { Ably::Models::Message.new(id: id, data: data) } + expect { channel.publish message_arr }.to raise_error do |error| + expect(error.code).to eql(40031) # Invalid publish request (invalid client-specified id), see https://github.com/ably/ably-common/pull/30 + end + end + + specify 'for multiple messages in one publish operation with IDs following the required format described in RSL1k1 (#RSL1k3)' do + pending 'idempotency rolled out to global cluster' + + message_arr = 3.times.map { |index| Ably::Models::Message.new(id: "#{id}:#{index}", data: data) } + channel.publish message_arr + expect(channel.history.items[0].id).to eql("{id}:0") + expect(channel.history.items[2].id).to eql("{id}:2") + expect(channel.history.items.length).to eql(3) + end + end + + specify 'idempotent publishing is disabled by default with 1.1 (#TO3n)' do + client = Ably::Rest::Client.new(key: api_key, protocol: protocol) + expect(client.idempotent_rest_publishing).to be_falsey + end + + specify 'idempotent publishing is enabled by default with 1.2 (#TO3n)' do + stub_const 'Ably::VERSION', '1.2.0' + client = Ably::Rest::Client.new(key: api_key, protocol: protocol) + expect(client.idempotent_rest_publishing).to be_truthy + end + + context 'when idempotent publishing is enabled in the client library ClientOptions (#TO3n)' do + let(:client_options) { default_client_options.merge(idempotent_rest_publishing: true, log_level: :error) } + + context 'when there is a network failure triggering an automatic retry (#RSL1k4)' do + def mock_for_two_publish_failures + @failed_http_posts = 0 + allow(client).to receive(:can_fallback_to_alternate_ably_host?).and_return(true) + allow_any_instance_of(Faraday::Connection).to receive(:post) do |*args| + @failed_http_posts += 1 + if @failed_http_posts == 2 + # Ensure the 3rd requests operates as normal + allow_any_instance_of(Faraday::Connection).to receive(:post).and_call_original + end + raise Faraday::ClientError.new('Fake client error') + end + end + + context 'with Message object' do + let(:message) { Ably::Models::Message.new(data: data) } + before { mock_for_two_publish_failures } + + specify 'two REST publish retries result in only one message being published' do + pending 'idempotency rolled out to global cluster' + + channel.publish [message] + expect(channel.history.items.length).to eql(1) + expect(@failed_http_posts).to eql(2) + end + end + + context 'with #publish arguments only' do + before { mock_for_two_publish_failures } + + specify 'two REST publish retries result in only one message being published' do + pending 'idempotency rolled out to global cluster' + + channel.publish 'event', data + expect(channel.history.items.length).to eql(1) + expect(@failed_http_posts).to eql(2) + end + end + + context 'with explicitly provided message ID' do + let(:id) { random_str } + + before { mock_for_two_publish_failures } + + specify 'two REST publish retries result in only one message being published' do + pending 'idempotency rolled out to global cluster' + + channel.publish 'event', data, id: id + expect(channel.history.items.length).to eql(1) + expect(channel.history.items[0].id).to eql(id) + expect(@failed_http_posts).to eql(2) + end + end + + specify 'for multiple messages in one publish operation' do + pending 'idempotency rolled out to global cluster' + + message_arr = 3.times.map { Ably::Models::Message.new(data: data) } + 3.times { channel.publish message_arr } + expect(channel.history.items.length).to eql(message_arr.length) + end + end + + specify 'the ID is populated with a random ID and serial 0 from this lib (#RSL1k1)' do + channel.publish 'event' + expect(channel.history.items[0].id).to match(/^[A-Za-z0-9\+\/]+:0$/) + base_64_id = channel.history.items[0].id.split(':')[0] + expect(Base64.decode64(base_64_id).length).to eql(9) + end + + context 'when publishing a batch of messages' do + specify 'the ID is populated with a single random ID and sequence of serials from this lib (#RSL1k1)' do + pending 'idempotency rolled out to global cluster' + + message = { name: 'event' } + channel.publish [message, message, message] + expect(channel.history.items[0].length).to eql(3) + expect(channel.history.items[0].id).to match(/^[A-Za-z0-9\+\/]+:0$/) + expect(channel.history.items[2].id).to match(/^[A-Za-z0-9\+\/]+:2$/) + base_64_id = channel.history.items[0].id.split(':')[0] + expect(Base64.decode64(base_64_id).length).to eql(9) + end + end + end + end + context 'with unsupported data payload content type' do context 'Integer' do let(:data) { 1 } - it 'is raises an UnsupportedDataType 40011 exception' do + it 'is raises an UnsupportedDataType 40013 exception' do expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType) end end context 'Float' do let(:data) { 1.1 } - it 'is raises an UnsupportedDataType 40011 exception' do + it 'is raises an UnsupportedDataType 40013 exception' do expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType) end end context 'Boolean' do let(:data) { true } - it 'is raises an UnsupportedDataType 40011 exception' do + it 'is raises an UnsupportedDataType 40013 exception' do expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType) end end context 'False' do let(:data) { false } - it 'is raises an UnsupportedDataType 40011 exception' do + it 'is raises an UnsupportedDataType 40013 exception' do expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataType) end end end