ably-rest 0.7.1 → 0.7.3
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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Ably::Util
|
|
2
|
+
# PubSub class provides methods to publish & subscribe to events, with methods and naming
|
|
3
|
+
# intentionally different to EventEmitter as it is intended for private message handling
|
|
4
|
+
# within the client library.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# class Channel
|
|
8
|
+
# def messages
|
|
9
|
+
# @messages ||= PubSub.new
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# channel = Channel.new
|
|
14
|
+
# channel.messages.subscribe(:event) { |name| puts "Event message #{name} received" }
|
|
15
|
+
# channel.messages.publish :event, "Test"
|
|
16
|
+
# #=> "Event message Test received"
|
|
17
|
+
# channel.messages.remove :event
|
|
18
|
+
#
|
|
19
|
+
class PubSub
|
|
20
|
+
include Ably::Modules::EventEmitter
|
|
21
|
+
|
|
22
|
+
# Ensure new PubSub object does not share class instance variables
|
|
23
|
+
def self.new(options = {})
|
|
24
|
+
Class.new(PubSub).allocate.tap do |pub_sub_object|
|
|
25
|
+
pub_sub_object.send(:initialize, options)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inspect
|
|
30
|
+
"<#PubSub: @event_emitter_coerce_proc: #{self.class.event_emitter_coerce_proc.inspect}\n @callbacks: #{callbacks}>"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(options = {})
|
|
34
|
+
self.class.instance_eval do
|
|
35
|
+
configure_event_emitter options
|
|
36
|
+
|
|
37
|
+
alias_method :subscribe, :on
|
|
38
|
+
alias_method :publish, :trigger
|
|
39
|
+
alias_method :unsubscribe, :off
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Realtime::Channel, '#history', :event_machine do
|
|
5
|
+
vary_by_protocol do
|
|
6
|
+
let(:default_options) { options.merge(api_key: api_key, environment: environment, protocol: protocol) }
|
|
7
|
+
|
|
8
|
+
let(:client) { Ably::Realtime::Client.new(default_options) }
|
|
9
|
+
let(:channel) { client.channel(channel_name) }
|
|
10
|
+
|
|
11
|
+
let(:client2) { Ably::Realtime::Client.new(default_options) }
|
|
12
|
+
let(:channel2) { client2.channel(channel_name) }
|
|
13
|
+
|
|
14
|
+
let(:channel_name) { "persisted:#{random_str(2)}" }
|
|
15
|
+
let(:payload) { random_str }
|
|
16
|
+
let(:messages) { [] }
|
|
17
|
+
|
|
18
|
+
let(:options) { { :protocol => :json } }
|
|
19
|
+
|
|
20
|
+
it 'returns a Deferrable' do
|
|
21
|
+
channel.publish('event', payload) do |message|
|
|
22
|
+
history = channel.history
|
|
23
|
+
expect(history).to be_a(EventMachine::Deferrable)
|
|
24
|
+
history.callback do |messages|
|
|
25
|
+
expect(messages.count).to eql(1)
|
|
26
|
+
expect(messages).to be_a(Ably::Models::PaginatedResource)
|
|
27
|
+
stop_reactor
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'with a single client publishing and receiving' do
|
|
33
|
+
it 'retrieves real-time history' do
|
|
34
|
+
channel.publish('event', payload) do |message|
|
|
35
|
+
channel.history do |history|
|
|
36
|
+
expect(history.length).to eql(1)
|
|
37
|
+
expect(history[0].data).to eql(payload)
|
|
38
|
+
stop_reactor
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'with two clients publishing messages on the same channel' do
|
|
45
|
+
it 'retrieves real-time history on both channels' do
|
|
46
|
+
channel.publish('event', payload) do |message|
|
|
47
|
+
channel2.publish('event', payload) do |message|
|
|
48
|
+
channel.history do |history|
|
|
49
|
+
expect(history.length).to eql(2)
|
|
50
|
+
expect(history.map(&:data).uniq).to eql([payload])
|
|
51
|
+
|
|
52
|
+
channel2.history do |history_2|
|
|
53
|
+
expect(history_2.length).to eql(2)
|
|
54
|
+
stop_reactor
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'with lots of messages published with a single client and channel' do
|
|
63
|
+
let(:messages_sent) { 30 }
|
|
64
|
+
let(:rate_per_second) { 10 }
|
|
65
|
+
let(:limit) { 15 }
|
|
66
|
+
|
|
67
|
+
def ensure_message_history_direction_and_paging_is_correct(direction)
|
|
68
|
+
channel.history(direction: direction, limit: limit) do |history|
|
|
69
|
+
expect(history.length).to eql(limit)
|
|
70
|
+
limit.times do |index|
|
|
71
|
+
expect(history[index].data).to eql("history#{index}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
history.next_page do |history|
|
|
75
|
+
expect(history.length).to eql(limit)
|
|
76
|
+
limit.times do |index|
|
|
77
|
+
expect(history[index].data).to eql("history#{index + limit}")
|
|
78
|
+
end
|
|
79
|
+
expect(history.last_page?).to eql(true)
|
|
80
|
+
|
|
81
|
+
stop_reactor
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context 'as one ProtocolMessage' do
|
|
87
|
+
it 'retrieves history forwards with pagination through :limit option' do
|
|
88
|
+
messages_sent.times do |index|
|
|
89
|
+
channel.publish('event', "history#{index}") do
|
|
90
|
+
next unless index == messages_sent - 1
|
|
91
|
+
ensure_message_history_direction_and_paging_is_correct :forwards
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'retrieves history backwards with pagination through :limit option' do
|
|
97
|
+
messages_sent.times.to_a.reverse.each do |index|
|
|
98
|
+
channel.publish('event', "history#{index}") do
|
|
99
|
+
next unless index == 0
|
|
100
|
+
ensure_message_history_direction_and_paging_is_correct :backwards
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context 'in multiple ProtocolMessages', em_timeout: (30 / 10) + 5 do
|
|
107
|
+
it 'retrieves limited history forwards with pagination' do
|
|
108
|
+
messages_sent.times do |index|
|
|
109
|
+
EventMachine.add_timer(index.to_f / rate_per_second) do
|
|
110
|
+
channel.publish('event', "history#{index}") do
|
|
111
|
+
next unless index == messages_sent - 1
|
|
112
|
+
ensure_message_history_direction_and_paging_is_correct :forwards
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'retrieves limited history backwards with pagination' do
|
|
119
|
+
messages_sent.times.to_a.reverse.each do |index|
|
|
120
|
+
EventMachine.add_timer((messages_sent - index).to_f / rate_per_second) do
|
|
121
|
+
channel.publish('event', "history#{index}") do
|
|
122
|
+
next unless index == 0
|
|
123
|
+
ensure_message_history_direction_and_paging_is_correct :backwards if index == 0
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'and REST history' do
|
|
131
|
+
let(:batches) { 3 }
|
|
132
|
+
let(:messages_per_batch) { 10 }
|
|
133
|
+
|
|
134
|
+
it 'return the same results with unique matching message IDs' do
|
|
135
|
+
batches.times do |batch|
|
|
136
|
+
EventMachine.add_timer(batch.to_f / batches.to_f) do
|
|
137
|
+
messages_per_batch.times { channel.publish('event', 'data') }
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
channel.subscribe('event') do |message|
|
|
142
|
+
messages << message
|
|
143
|
+
if messages.count == batches * messages_per_batch
|
|
144
|
+
channel.history do |history|
|
|
145
|
+
expect(history.map(&:id).sort).to eql(messages.map(&:id).sort)
|
|
146
|
+
stop_reactor
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Realtime::Channel, :event_machine do
|
|
5
|
+
vary_by_protocol do
|
|
6
|
+
let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
|
7
|
+
let(:client_options) { default_options }
|
|
8
|
+
|
|
9
|
+
let(:client) { Ably::Realtime::Client.new(client_options) }
|
|
10
|
+
let(:channel_name) { random_str }
|
|
11
|
+
let(:payload) { random_str }
|
|
12
|
+
let(:channel) { client.channel(channel_name) }
|
|
13
|
+
let(:messages) { [] }
|
|
14
|
+
|
|
15
|
+
describe 'initialization' do
|
|
16
|
+
context 'with :connect_automatically option set to false on connection' do
|
|
17
|
+
let(:client) do
|
|
18
|
+
Ably::Realtime::Client.new(default_options.merge(connect_automatically: false))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'remains initialized when accessing a channel' do
|
|
22
|
+
client.channel('test')
|
|
23
|
+
EventMachine.add_timer(2) do
|
|
24
|
+
expect(client.connection).to be_initialized
|
|
25
|
+
stop_reactor
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'opens a connection implicitly on #attach' do
|
|
30
|
+
client.channel('test').attach do
|
|
31
|
+
expect(client.connection).to be_connected
|
|
32
|
+
stop_reactor
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'opens a connection implicitly when accessing #presence' do
|
|
37
|
+
client.channel('test').tap do |channel|
|
|
38
|
+
channel.on(:attached) do
|
|
39
|
+
expect(client.connection).to be_connected
|
|
40
|
+
stop_reactor
|
|
41
|
+
end
|
|
42
|
+
channel.presence
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#attach' do
|
|
49
|
+
it 'emits attaching then attached events' do
|
|
50
|
+
channel.once(:attaching) do
|
|
51
|
+
channel.once(:attached) do
|
|
52
|
+
stop_reactor
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
channel.attach
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'ignores subsequent #attach calls but calls the success callback if provided' do
|
|
60
|
+
channel.once(:attaching) do
|
|
61
|
+
channel.attach
|
|
62
|
+
channel.once(:attached) do
|
|
63
|
+
channel.attach do
|
|
64
|
+
stop_reactor
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
channel.attach
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'attaches to a channel' do
|
|
73
|
+
channel.attach
|
|
74
|
+
channel.on(:attached) do
|
|
75
|
+
expect(channel.state).to eq(:attached)
|
|
76
|
+
stop_reactor
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'attaches to a channel and calls the provided block' do
|
|
81
|
+
channel.attach do
|
|
82
|
+
expect(channel.state).to eq(:attached)
|
|
83
|
+
stop_reactor
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'returns a Deferrable' do
|
|
88
|
+
expect(channel.attach).to be_a(EventMachine::Deferrable)
|
|
89
|
+
stop_reactor
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'calls the Deferrable callback on success' do
|
|
93
|
+
channel.attach.callback do |channel|
|
|
94
|
+
expect(channel).to be_a(Ably::Realtime::Channel)
|
|
95
|
+
expect(channel.state).to eq(:attached)
|
|
96
|
+
stop_reactor
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'when state is :failed' do
|
|
101
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
|
102
|
+
|
|
103
|
+
it 'reattaches' do
|
|
104
|
+
channel.attach do
|
|
105
|
+
channel.transition_state_machine :failed, RuntimeError.new
|
|
106
|
+
expect(channel).to be_failed
|
|
107
|
+
channel.attach do
|
|
108
|
+
expect(channel).to be_attached
|
|
109
|
+
stop_reactor
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context 'when state is :detaching' do
|
|
116
|
+
it 'moves straight to attaching and skips detached' do
|
|
117
|
+
channel.once(:detaching) do
|
|
118
|
+
channel.once(:detached) { raise 'Detach should not have been reached' }
|
|
119
|
+
|
|
120
|
+
channel.once(:attaching) do
|
|
121
|
+
channel.once(:attached) do
|
|
122
|
+
channel.off
|
|
123
|
+
stop_reactor
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
channel.attach
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
channel.attach do
|
|
131
|
+
channel.detach
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'with many connections and many channels on each simultaneously' do
|
|
137
|
+
let(:connection_count) { 30 }
|
|
138
|
+
let(:channel_count) { 10 }
|
|
139
|
+
let(:permutation_count) { connection_count * channel_count }
|
|
140
|
+
let(:channel_connection_ids) { [] }
|
|
141
|
+
|
|
142
|
+
it 'attaches all channels', em_timeout: 15 do
|
|
143
|
+
connection_count.times.map do
|
|
144
|
+
Ably::Realtime::Client.new(default_options)
|
|
145
|
+
end.each do |client|
|
|
146
|
+
channel_count.times.map do |index|
|
|
147
|
+
client.channel("channel-#{index}").attach do
|
|
148
|
+
channel_connection_ids << "#{client.connection.id}:#{index}"
|
|
149
|
+
next unless channel_connection_ids.count == permutation_count
|
|
150
|
+
|
|
151
|
+
expect(channel_connection_ids.uniq.count).to eql(permutation_count)
|
|
152
|
+
stop_reactor
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context 'failure as a result of insufficient key permissions' do
|
|
160
|
+
let(:restricted_client) do
|
|
161
|
+
Ably::Realtime::Client.new(default_options.merge(api_key: restricted_api_key, log_level: :fatal))
|
|
162
|
+
end
|
|
163
|
+
let(:restricted_channel) { restricted_client.channel("cannot_subscribe") }
|
|
164
|
+
|
|
165
|
+
it 'triggers failed event' do
|
|
166
|
+
restricted_channel.attach
|
|
167
|
+
restricted_channel.on(:failed) do |error|
|
|
168
|
+
expect(restricted_channel.state).to eq(:failed)
|
|
169
|
+
expect(error.status).to eq(401)
|
|
170
|
+
stop_reactor
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'calls the errback of the returned Deferrable' do
|
|
175
|
+
restricted_channel.attach.errback do |channel, error|
|
|
176
|
+
expect(restricted_channel.state).to eq(:failed)
|
|
177
|
+
expect(error.status).to eq(401)
|
|
178
|
+
stop_reactor
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'triggers an error event' do
|
|
183
|
+
restricted_channel.attach
|
|
184
|
+
restricted_channel.on(:error) do |error|
|
|
185
|
+
expect(restricted_channel.state).to eq(:failed)
|
|
186
|
+
expect(error.status).to eq(401)
|
|
187
|
+
stop_reactor
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'updates the error_reason' do
|
|
192
|
+
restricted_channel.attach
|
|
193
|
+
restricted_channel.on(:failed) do
|
|
194
|
+
expect(restricted_channel.error_reason.status).to eq(401)
|
|
195
|
+
stop_reactor
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe '#detach' do
|
|
202
|
+
it 'detaches from a channel' do
|
|
203
|
+
channel.attach do
|
|
204
|
+
channel.detach
|
|
205
|
+
channel.on(:detached) do
|
|
206
|
+
expect(channel.state).to eq(:detached)
|
|
207
|
+
stop_reactor
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'detaches from a channel and calls the provided block' do
|
|
213
|
+
channel.attach do |chan|
|
|
214
|
+
chan.detach do |detached_chan|
|
|
215
|
+
expect(detached_chan.state).to eq(:detached)
|
|
216
|
+
stop_reactor
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it 'emits :detaching then :detached events' do
|
|
222
|
+
channel.once(:detaching) do
|
|
223
|
+
channel.once(:detached) do
|
|
224
|
+
stop_reactor
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
channel.attach do
|
|
229
|
+
channel.detach
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'returns a Deferrable' do
|
|
234
|
+
expect(channel.attach).to be_a(EventMachine::Deferrable)
|
|
235
|
+
stop_reactor
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'calls the Deferrable callback on success' do
|
|
239
|
+
channel.attach do
|
|
240
|
+
channel.detach.callback do |channel|
|
|
241
|
+
expect(channel).to be_a(Ably::Realtime::Channel)
|
|
242
|
+
expect(channel.state).to eq(:detached)
|
|
243
|
+
stop_reactor
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
context 'when state is :failed' do
|
|
249
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
|
250
|
+
|
|
251
|
+
it 'raises an exception' do
|
|
252
|
+
channel.attach do
|
|
253
|
+
channel.transition_state_machine :failed, RuntimeError.new
|
|
254
|
+
expect(channel).to be_failed
|
|
255
|
+
expect { channel.detach }.to raise_error Ably::Exceptions::StateChangeError
|
|
256
|
+
stop_reactor
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
context 'when state is :attaching' do
|
|
262
|
+
it 'moves straight to :detaching state and skips :attached' do
|
|
263
|
+
channel.once(:attaching) do
|
|
264
|
+
channel.once(:attached) { raise 'Attached should never be reached' }
|
|
265
|
+
|
|
266
|
+
channel.once(:detaching) do
|
|
267
|
+
channel.once(:detached) do
|
|
268
|
+
stop_reactor
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
channel.detach
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
channel.attach
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context 'when state is :detaching' do
|
|
280
|
+
it 'ignores subsequent #detach calls but calls the callback if provided' do
|
|
281
|
+
channel.once(:detaching) do
|
|
282
|
+
channel.detach
|
|
283
|
+
channel.once(:detached) do
|
|
284
|
+
channel.detach do
|
|
285
|
+
stop_reactor
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
channel.attach do
|
|
291
|
+
channel.detach
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe 'channel recovery in :attaching state' do
|
|
298
|
+
context 'the transport is disconnected before the ATTACHED protocol message is received' do
|
|
299
|
+
skip 'attach times out and fails if not ATTACHED protocol message received'
|
|
300
|
+
skip 'channel is ATTACHED if ATTACHED protocol message is later received'
|
|
301
|
+
skip 'sends an ATTACH protocol message in response to a channel message being received on the attaching channel'
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
context '#publish' do
|
|
306
|
+
context 'when attached' do
|
|
307
|
+
it 'publishes messages' do
|
|
308
|
+
channel.attach do
|
|
309
|
+
3.times { channel.publish('event', payload) }
|
|
310
|
+
end
|
|
311
|
+
channel.subscribe do |message|
|
|
312
|
+
messages << message if message.data == payload
|
|
313
|
+
stop_reactor if messages.count == 3
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
context 'when not yet attached' do
|
|
319
|
+
it 'publishes queued messages once attached' do
|
|
320
|
+
3.times { channel.publish('event', random_str) }
|
|
321
|
+
channel.subscribe do |message|
|
|
322
|
+
messages << message if message.name == 'event'
|
|
323
|
+
stop_reactor if messages.count == 3
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it 'publishes queued messages within a single protocol message' do
|
|
328
|
+
3.times { channel.publish('event', random_str) }
|
|
329
|
+
channel.subscribe do |message|
|
|
330
|
+
messages << message if message.name == 'event'
|
|
331
|
+
next unless messages.length == 3
|
|
332
|
+
|
|
333
|
+
# All 3 messages should be batched into a single Protocol Message by the client library
|
|
334
|
+
# message.id = "{protocol_message.id}:{protocol_message_index}"
|
|
335
|
+
# Check that all messages share the same protocol_message.id
|
|
336
|
+
message_id = messages.map { |msg| msg.id.split(':')[0] }
|
|
337
|
+
expect(message_id.uniq.count).to eql(1)
|
|
338
|
+
|
|
339
|
+
# Check that messages use index 0,1,2 in the ID
|
|
340
|
+
message_indexes = messages.map { |msg| msg.id.split(':')[1] }
|
|
341
|
+
expect(message_indexes).to include("0", "1", "2")
|
|
342
|
+
stop_reactor
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
describe '#subscribe' do
|
|
349
|
+
context 'with an event argument' do
|
|
350
|
+
it 'subscribes for a single event' do
|
|
351
|
+
channel.subscribe('click') do |message|
|
|
352
|
+
expect(message.data).to eql('data')
|
|
353
|
+
stop_reactor
|
|
354
|
+
end
|
|
355
|
+
channel.publish('click', 'data')
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
context 'with no event argument' do
|
|
360
|
+
it 'subscribes for all events' do
|
|
361
|
+
channel.subscribe do |message|
|
|
362
|
+
expect(message.data).to eql('data')
|
|
363
|
+
stop_reactor
|
|
364
|
+
end
|
|
365
|
+
channel.publish('click', 'data')
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
context 'many times with different event names' do
|
|
370
|
+
it 'filters events accordingly to each callback' do
|
|
371
|
+
click_callback = proc { |message| messages << message }
|
|
372
|
+
|
|
373
|
+
channel.subscribe('click', &click_callback)
|
|
374
|
+
channel.subscribe('move', &click_callback)
|
|
375
|
+
channel.subscribe('press', &click_callback)
|
|
376
|
+
|
|
377
|
+
channel.attach do
|
|
378
|
+
channel.publish('click', 'data')
|
|
379
|
+
channel.publish('move', 'data')
|
|
380
|
+
channel.publish('press', 'data') do
|
|
381
|
+
EventMachine.add_timer(2) do
|
|
382
|
+
expect(messages.count).to eql(3)
|
|
383
|
+
stop_reactor
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
describe '#unsubscribe' do
|
|
392
|
+
context 'with an event argument' do
|
|
393
|
+
it 'unsubscribes for a single event' do
|
|
394
|
+
channel.subscribe('click') { raise 'Should not have been called' }
|
|
395
|
+
channel.unsubscribe('click')
|
|
396
|
+
|
|
397
|
+
channel.publish('click', 'data') do
|
|
398
|
+
EventMachine.add_timer(1) do
|
|
399
|
+
stop_reactor
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
context 'with no event argument' do
|
|
406
|
+
it 'unsubscribes for a single event' do
|
|
407
|
+
channel.subscribe { raise 'Should not have been called' }
|
|
408
|
+
channel.unsubscribe
|
|
409
|
+
|
|
410
|
+
channel.publish('click', 'data') do
|
|
411
|
+
EventMachine.add_timer(1) do
|
|
412
|
+
stop_reactor
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
context 'when connection state changes to' do
|
|
420
|
+
context ':failed' do
|
|
421
|
+
let(:connection_error) { Ably::Exceptions::ConnectionError.new('forced failure', 500, 50000) }
|
|
422
|
+
let(:client_options) { default_options.merge(log_level: :none) }
|
|
423
|
+
|
|
424
|
+
def fake_error(error)
|
|
425
|
+
client.connection.manager.error_received_from_server error
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
context 'an :attached channel' do
|
|
429
|
+
it 'transitions state to :failed' do
|
|
430
|
+
channel.attach do
|
|
431
|
+
channel.on(:failed) do |error|
|
|
432
|
+
expect(error).to eql(connection_error)
|
|
433
|
+
stop_reactor
|
|
434
|
+
end
|
|
435
|
+
fake_error connection_error
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
it 'triggers an error event on the channel' do
|
|
440
|
+
channel.attach do
|
|
441
|
+
channel.on(:error) do |error|
|
|
442
|
+
expect(error).to eql(connection_error)
|
|
443
|
+
stop_reactor
|
|
444
|
+
end
|
|
445
|
+
fake_error connection_error
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
it 'updates the channel error_reason' do
|
|
450
|
+
channel.attach do
|
|
451
|
+
channel.on(:failed) do |error|
|
|
452
|
+
expect(channel.error_reason).to eql(connection_error)
|
|
453
|
+
stop_reactor
|
|
454
|
+
end
|
|
455
|
+
fake_error connection_error
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
context 'a :detached channel' do
|
|
461
|
+
it 'remains in the :detached state' do
|
|
462
|
+
channel.attach do
|
|
463
|
+
channel.on(:failed) { raise 'Failed state should not have been reached' }
|
|
464
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
|
465
|
+
|
|
466
|
+
channel.detach do
|
|
467
|
+
EventMachine.add_timer(1) do
|
|
468
|
+
expect(channel).to be_detached
|
|
469
|
+
stop_reactor
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
fake_error connection_error
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
context 'a :failed channel' do
|
|
479
|
+
let(:original_error) { RuntimeError.new }
|
|
480
|
+
|
|
481
|
+
it 'remains in the :failed state and ignores the failure error' do
|
|
482
|
+
channel.attach do
|
|
483
|
+
channel.on(:error) do
|
|
484
|
+
channel.on(:failed) { raise 'Failed state should not have been reached' }
|
|
485
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
|
486
|
+
|
|
487
|
+
EventMachine.add_timer(1) do
|
|
488
|
+
expect(channel).to be_failed
|
|
489
|
+
expect(channel.error_reason).to eql(original_error)
|
|
490
|
+
stop_reactor
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
fake_error connection_error
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
channel.transition_state_machine :failed, original_error
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
context ':closed' do
|
|
503
|
+
context 'an :attached channel' do
|
|
504
|
+
it 'transitions state to :detached' do
|
|
505
|
+
channel.attach do
|
|
506
|
+
channel.on(:detached) do
|
|
507
|
+
stop_reactor
|
|
508
|
+
end
|
|
509
|
+
client.connection.close
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
context 'a :detached channel' do
|
|
515
|
+
it 'remains in the :detached state' do
|
|
516
|
+
channel.attach do
|
|
517
|
+
channel.detach do
|
|
518
|
+
channel.on(:detached) { raise 'Detached state should not have been reached' }
|
|
519
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
|
520
|
+
|
|
521
|
+
EventMachine.add_timer(1) do
|
|
522
|
+
expect(channel).to be_detached
|
|
523
|
+
stop_reactor
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
client.connection.close
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
context 'a :failed channel' do
|
|
533
|
+
let(:original_error) { RuntimeError.new }
|
|
534
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
|
535
|
+
|
|
536
|
+
it 'remains in the :failed state and retains the error_reason' do
|
|
537
|
+
channel.attach do
|
|
538
|
+
channel.once(:error) do
|
|
539
|
+
channel.on(:detached) { raise 'Detached state should not have been reached' }
|
|
540
|
+
channel.on(:error) { raise 'Error should not have been emitted' }
|
|
541
|
+
|
|
542
|
+
EventMachine.add_timer(1) do
|
|
543
|
+
expect(channel).to be_failed
|
|
544
|
+
expect(channel.error_reason).to eql(original_error)
|
|
545
|
+
stop_reactor
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
client.connection.close
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
channel.transition_state_machine :failed, original_error
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
end
|