ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. 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,3 @@
1
+ module Ably
2
+ VERSION = '0.7.3'
3
+ 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