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.
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