ably-rest 0.7.3 → 0.7.5

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 (69) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +1 -0
  3. data/SPEC.md +480 -472
  4. data/lib/ably-rest.rb +1 -1
  5. data/lib/submodules/ably-ruby/LICENSE.txt +1 -1
  6. data/lib/submodules/ably-ruby/README.md +107 -24
  7. data/lib/submodules/ably-ruby/SPEC.md +531 -398
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +24 -16
  9. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +9 -0
  10. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +17 -9
  11. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +12 -8
  12. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +18 -10
  13. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -4
  14. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +4 -3
  15. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +31 -2
  16. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +77 -0
  17. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +71 -0
  18. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +41 -0
  19. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +28 -8
  20. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +0 -5
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -29
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +54 -11
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +7 -2
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +41 -9
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +72 -24
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -6
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +74 -208
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +264 -0
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +59 -0
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
  35. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +6 -2
  37. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  38. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +3 -1
  39. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +18 -0
  40. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +2 -2
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +28 -6
  43. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
  44. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +55 -10
  45. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +32 -0
  46. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +456 -96
  47. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +2 -2
  48. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +2 -2
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +96 -7
  50. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +8 -0
  51. data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +71 -0
  52. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +1 -1
  53. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  54. data/lib/submodules/ably-ruby/spec/support/test_app.rb +13 -7
  55. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +15 -14
  56. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +4 -4
  57. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +17 -17
  58. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +4 -4
  59. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +28 -9
  60. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +50 -0
  61. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +76 -2
  62. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +51 -20
  63. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +3 -3
  64. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +30 -0
  65. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +52 -26
  66. data/lib/submodules/ably-ruby/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
  67. data/spec/spec_helper.rb +5 -0
  68. metadata +12 -4
  69. data/lib/submodules/ably-ruby/.ruby-version.old +0 -1
@@ -28,7 +28,7 @@ describe Ably::Models::PresenceMessage do
28
28
  end
29
29
 
30
30
  context 'with a protocol message with a different connectionId' do
31
- let(:model) { subject.new({ 'connectionId' => model_connection_id }, protocol_message) }
31
+ let(:model) { subject.new({ 'connectionId' => model_connection_id }, protocol_message: protocol_message) }
32
32
 
33
33
  it 'uses the model value' do
34
34
  expect(model.connection_id).to eql(model_connection_id)
@@ -46,7 +46,7 @@ describe Ably::Models::PresenceMessage do
46
46
  end
47
47
 
48
48
  context 'with a protocol message with a connectionId' do
49
- let(:model) { subject.new({ }, protocol_message) }
49
+ let(:model) { subject.new({ }, protocol_message: protocol_message) }
50
50
 
51
51
  it 'uses the model value' do
52
52
  expect(model.connection_id).to eql(protocol_connection_id)
@@ -63,8 +63,8 @@ describe Ably::Models::PresenceMessage do
63
63
  end
64
64
 
65
65
  context 'with the same client id across multiple connections' do
66
- let(:connection_1) { subject.new({ client_id: 'same', connection_id: 'unique' }, protocol_message) }
67
- let(:connection_2) { subject.new({ client_id: 'same', connection_id: 'different' }, protocol_message) }
66
+ let(:connection_1) { subject.new({ client_id: 'same', connection_id: 'unique' }, protocol_message: protocol_message) }
67
+ let(:connection_2) { subject.new({ client_id: 'same', connection_id: 'different' }, protocol_message: protocol_message) }
68
68
 
69
69
  it 'is unique' do
70
70
  expect(connection_1.member_key).to_not eql(connection_2.member_key)
@@ -72,8 +72,8 @@ describe Ably::Models::PresenceMessage do
72
72
  end
73
73
 
74
74
  context 'with a single connection and different client_ids' do
75
- let(:client_1) { subject.new({ client_id: 'unique', connection_id: 'same' }, protocol_message) }
76
- let(:client_2) { subject.new({ client_id: 'different', connection_id: 'same' }, protocol_message) }
75
+ let(:client_1) { subject.new({ client_id: 'unique', connection_id: 'same' }, protocol_message: protocol_message) }
76
+ let(:client_2) { subject.new({ client_id: 'different', connection_id: 'same' }, protocol_message: protocol_message) }
77
77
 
78
78
  it 'is unique' do
79
79
  expect(client_1.member_key).to_not eql(client_2.member_key)
@@ -82,7 +82,7 @@ describe Ably::Models::PresenceMessage do
82
82
  end
83
83
 
84
84
  context '#timestamp' do
85
- let(:model) { subject.new({}, protocol_message) }
85
+ let(:model) { subject.new({}, protocol_message: protocol_message) }
86
86
  it 'retrieves attribute :timestamp as a Time object from ProtocolMessage' do
87
87
  expect(model.timestamp).to be_a(Time)
88
88
  expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
@@ -90,7 +90,7 @@ describe Ably::Models::PresenceMessage do
90
90
  end
91
91
 
92
92
  context 'Java naming', :api_private do
93
- let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
93
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message: protocol_message) }
94
94
 
95
95
  it 'converts the attribute to ruby symbol naming convention' do
96
96
  expect(model.client_id).to eql('joe')
@@ -99,7 +99,7 @@ describe Ably::Models::PresenceMessage do
99
99
 
100
100
  context 'with action', :api_private do
101
101
  context 'absent' do
102
- let(:model) { subject.new({ action: 0 }, protocol_message) }
102
+ let(:model) { subject.new({ action: 0 }, protocol_message: protocol_message) }
103
103
 
104
104
  it 'provides action as an Enum' do
105
105
  expect(model.action).to eq(:absent)
@@ -107,7 +107,7 @@ describe Ably::Models::PresenceMessage do
107
107
  end
108
108
 
109
109
  context 'enter' do
110
- let(:model) { subject.new({ action: 2 }, protocol_message) }
110
+ let(:model) { subject.new({ action: 2 }, protocol_message: protocol_message) }
111
111
 
112
112
  it 'provides action as an Enum' do
113
113
  expect(model.action).to eq(:enter)
@@ -116,7 +116,7 @@ describe Ably::Models::PresenceMessage do
116
116
  end
117
117
 
118
118
  context 'without action', :api_private do
119
- let(:model) { subject.new({}, protocol_message) }
119
+ let(:model) { subject.new({}, protocol_message: protocol_message) }
120
120
 
121
121
  it 'raises an exception when accessed' do
122
122
  expect { model.action }.to raise_error KeyError
@@ -129,7 +129,7 @@ describe Ably::Models::PresenceMessage do
129
129
  let(:encoded_value) { value.encode(encoding) }
130
130
  let(:value) { random_str }
131
131
  let(:options) { { attribute.to_sym => encoded_value } }
132
- let(:model) { subject.new(options, protocol_message) }
132
+ let(:model) { subject.new(options, protocol_message: protocol_message) }
133
133
  let(:model_attribute) { model.public_send(attribute) }
134
134
 
135
135
  context 'as UTF_8 string' do
@@ -191,7 +191,7 @@ describe Ably::Models::PresenceMessage do
191
191
  let(:json_object) { JSON.parse(model.to_json) }
192
192
 
193
193
  context 'with valid data' do
194
- let(:model) { subject.new({ action: 'enter', clientId: 'joe' }, protocol_message) }
194
+ let(:model) { subject.new({ action: 'enter', clientId: 'joe' }, protocol_message: protocol_message) }
195
195
 
196
196
  it 'converts the attribute back to Java mixedCase notation using string keys' do
197
197
  expect(json_object["clientId"]).to eql('joe')
@@ -199,7 +199,7 @@ describe Ably::Models::PresenceMessage do
199
199
  end
200
200
 
201
201
  context 'with invalid data' do
202
- let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
202
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message: protocol_message) }
203
203
 
204
204
  it 'raises an exception' do
205
205
  expect { model.to_json }.to raise_error KeyError, /cannot generate a valid Hash/
@@ -208,7 +208,7 @@ describe Ably::Models::PresenceMessage do
208
208
 
209
209
  context 'with binary data' do
210
210
  let(:data) { MessagePack.pack(random_str(32)) }
211
- let(:model) { subject.new({ action: 'enter', data: data }, protocol_message) }
211
+ let(:model) { subject.new({ action: 'enter', data: data }, protocol_message: protocol_message) }
212
212
 
213
213
  it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
214
214
  expect(json_object["data"]).to eql(::Base64.encode64(data))
@@ -319,7 +319,7 @@ describe Ably::Models::PresenceMessage do
319
319
  end
320
320
 
321
321
  context 'with ProtocolMessage' do
322
- subject { Ably::Models.PresenceMessage(json, protocol_message) }
322
+ subject { Ably::Models.PresenceMessage(json, protocol_message: protocol_message) }
323
323
 
324
324
  it 'returns a PresenceMessage object' do
325
325
  expect(subject).to be_a(Ably::Models::PresenceMessage)
@@ -363,7 +363,7 @@ describe Ably::Models::PresenceMessage do
363
363
  end
364
364
 
365
365
  context 'with ProtocolMessage' do
366
- subject { Ably::Models.PresenceMessage(message, protocol_message) }
366
+ subject { Ably::Models.PresenceMessage(message, protocol_message: protocol_message) }
367
367
 
368
368
  it 'returns a PresenceMessage object' do
369
369
  expect(subject).to be_a(Ably::Models::PresenceMessage)
@@ -64,22 +64,22 @@ describe Ably::Models::Stat do
64
64
 
65
65
  describe '#from_interval_id' do
66
66
  it 'converts a month interval_id 2014-02 into a Time object in UTC 0' do
67
- expect(subject.from_interval_id('2014-02')).to eql(Time.new(2014, 2))
67
+ expect(subject.from_interval_id('2014-02')).to eql(Time.gm(2014, 2))
68
68
  expect(subject.from_interval_id('2014-02').utc_offset).to eql(0)
69
69
  end
70
70
 
71
71
  it 'converts a day interval_id 2014-02-03 into a Time object in UTC 0' do
72
- expect(subject.from_interval_id('2014-02-03')).to eql(Time.new(2014, 2, 3))
72
+ expect(subject.from_interval_id('2014-02-03')).to eql(Time.gm(2014, 2, 3))
73
73
  expect(subject.from_interval_id('2014-02-03').utc_offset).to eql(0)
74
74
  end
75
75
 
76
76
  it 'converts an hour interval_id 2014-02-03:05 into a Time object in UTC 0' do
77
- expect(subject.from_interval_id('2014-02-03:05')).to eql(Time.new(2014, 2, 3, 5))
77
+ expect(subject.from_interval_id('2014-02-03:05')).to eql(Time.gm(2014, 2, 3, 5))
78
78
  expect(subject.from_interval_id('2014-02-03:05').utc_offset).to eql(0)
79
79
  end
80
80
 
81
81
  it 'converts a minute interval_id 2014-02-03:05:06 into a Time object in UTC 0' do
82
- expect(subject.from_interval_id('2014-02-03:05:06')).to eql(Time.new(2014, 2, 3, 5, 6))
82
+ expect(subject.from_interval_id('2014-02-03:05:06')).to eql(Time.gm(2014, 2, 3, 5, 6))
83
83
  expect(subject.from_interval_id('2014-02-03:05:06').utc_offset).to eql(0)
84
84
  end
85
85
 
@@ -14,6 +14,10 @@ describe Ably::Modules::AsyncWrapper, :api_private do
14
14
  def block=(block)
15
15
  @block = block
16
16
  end
17
+
18
+ def logger
19
+ true
20
+ end
17
21
  end
18
22
  end
19
23
  let(:subject) { class_with_module.new }
@@ -33,7 +37,15 @@ describe Ably::Modules::AsyncWrapper, :api_private do
33
37
  end
34
38
  end
35
39
 
36
- it 'calls the success_callback block with result when provided' do
40
+ it 'returns a SafeDeferrable that catches and logs exceptions in the provided callbacks' do
41
+ run_reactor do
42
+ deferrable = subject.operation
43
+ expect(deferrable).to be_a(Ably::Util::SafeDeferrable)
44
+ stop_reactor
45
+ end
46
+ end
47
+
48
+ it 'calls the provided block with result when provided' do
37
49
  run_reactor do
38
50
  subject.operation do |result|
39
51
  expect(result).to eql(result)
@@ -42,10 +54,20 @@ describe Ably::Modules::AsyncWrapper, :api_private do
42
54
  end
43
55
  end
44
56
 
45
- it 'returns a deferrable that succeeds with result' do
57
+ it 'catches exceptions in the provided block and logs them to logger' do
58
+ run_reactor do
59
+ subject.operation do |result|
60
+ raise 'Intentional exception'
61
+ end
62
+ expect(subject.logger).to receive(:error).with(/Intentional exception/) do
63
+ stop_reactor
64
+ end
65
+ end
66
+ end
67
+
68
+ it 'returns a SafeDeferrable that calls the callback block' do
46
69
  run_reactor do
47
70
  deferrable = subject.operation
48
- expect(deferrable).to be_a(EventMachine::Deferrable)
49
71
  deferrable.callback do |result|
50
72
  expect(result).to eql(result)
51
73
  stop_reactor
@@ -56,7 +78,6 @@ describe Ably::Modules::AsyncWrapper, :api_private do
56
78
  it 'does not call the errback' do
57
79
  run_reactor do
58
80
  deferrable = subject.operation
59
- expect(deferrable).to be_a(EventMachine::Deferrable)
60
81
  deferrable.callback do |result|
61
82
  expect(result).to eql(result)
62
83
  EventMachine.add_timer(sleep_time * 2) { stop_reactor }
@@ -88,10 +109,9 @@ describe Ably::Modules::AsyncWrapper, :api_private do
88
109
  end
89
110
  end
90
111
 
91
- it 'calls the errback block of the deferrable' do
112
+ it 'calls the errback block of the SafeDeferrable' do
92
113
  run_reactor do
93
114
  deferrable = subject.operation
94
- expect(deferrable).to be_a(EventMachine::Deferrable)
95
115
  deferrable.errback do |error|
96
116
  expect(error).to be_a(RuntimeError)
97
117
  expect(error.message).to match(/Intentional/)
@@ -100,7 +120,7 @@ describe Ably::Modules::AsyncWrapper, :api_private do
100
120
  end
101
121
  end
102
122
 
103
- it 'does not call the success_callback block' do
123
+ it 'does not call the provided block' do
104
124
  run_reactor do
105
125
  subject.operation do |result|
106
126
  raise 'Callback should not have been called'
@@ -109,10 +129,9 @@ describe Ably::Modules::AsyncWrapper, :api_private do
109
129
  end
110
130
  end
111
131
 
112
- it 'does not call the callback block of the deferrable' do
132
+ it 'does not call the callback block of the SafeDeferrable' do
113
133
  run_reactor do
114
134
  deferrable = subject.operation
115
- expect(deferrable).to be_a(EventMachine::Deferrable)
116
135
  deferrable.callback do |result|
117
136
  raise 'Callback should not have been called'
118
137
  end
@@ -7,6 +7,7 @@ describe Ably::Modules::EventEmitter do
7
7
  Class.new do
8
8
  include Ably::Modules::EventEmitter
9
9
  configure_event_emitter callback_opts
10
+ def logger; end
10
11
  end
11
12
  end
12
13
  let(:obj) { double('example') }
@@ -124,6 +125,35 @@ describe Ably::Modules::EventEmitter do
124
125
  end
125
126
  end
126
127
 
128
+ context '#on' do
129
+ it 'calls the block every time an event is emitted only' do
130
+ block_called = 0
131
+ subject.on('event') { block_called += 1 }
132
+ 3.times { subject.trigger 'event', 'data' }
133
+ expect(block_called).to eql(3)
134
+ end
135
+
136
+ it 'catches exceptions in the provided block, logs the error and continues' do
137
+ expect(subject.logger).to receive(:error).with(/Intentional exception/)
138
+ subject.on(:event) { raise 'Intentional exception' }
139
+ subject.trigger :event
140
+ end
141
+ end
142
+
143
+ context '#unsafe_on', api_private: true do
144
+ it 'calls the block every time an event is emitted only' do
145
+ block_called = 0
146
+ subject.unsafe_on('event') { block_called += 1 }
147
+ 3.times { subject.trigger 'event', 'data' }
148
+ expect(block_called).to eql(3)
149
+ end
150
+
151
+ it 'does not catch exceptions in provided blocks' do
152
+ subject.unsafe_on(:event) { raise 'Intentional exception' }
153
+ expect { subject.trigger :event }.to raise_error(/Intentional exception/)
154
+ end
155
+ end
156
+
127
157
  context '#once' do
128
158
  it 'calls the block the first time an event is emitted only' do
129
159
  block_called = 0
@@ -139,6 +169,26 @@ describe Ably::Modules::EventEmitter do
139
169
  3.times { subject.trigger 'event', 'data' }
140
170
  expect(block_called).to eql(4)
141
171
  end
172
+
173
+ it 'catches exceptions in the provided block, logs the error and continues' do
174
+ expect(subject.logger).to receive(:error).with(/Intentional exception/)
175
+ subject.once(:event) { raise 'Intentional exception' }
176
+ subject.trigger :event
177
+ end
178
+ end
179
+
180
+ context '#unsafe_once' do
181
+ it 'calls the block the first time an event is emitted only' do
182
+ block_called = 0
183
+ subject.unsafe_once('event') { block_called += 1 }
184
+ 3.times { subject.trigger 'event', 'data' }
185
+ expect(block_called).to eql(1)
186
+ end
187
+
188
+ it 'does not catch exceptions in provided blocks' do
189
+ subject.unsafe_once(:event) { raise 'Intentional exception' }
190
+ expect { subject.trigger :event }.to raise_error(/Intentional exception/)
191
+ end
142
192
  end
143
193
 
144
194
  context '#off' do
@@ -13,14 +13,17 @@ describe Ably::Modules::StateEmitter do
13
13
  )
14
14
  include Ably::Modules::StateEmitter
15
15
 
16
- def initialize
16
+ def initialize(logger)
17
17
  @state = :initializing
18
+ @logger = logger
18
19
  end
20
+
21
+ attr_reader :logger
19
22
  end
20
23
 
21
24
  let(:initial_state) { :initializing }
22
25
 
23
- subject { ExampleStateWithEventEmitter.new }
26
+ subject { ExampleStateWithEventEmitter.new(double('Logger').as_null_object) }
24
27
 
25
28
  specify '#state returns current state' do
26
29
  expect(subject.state).to eq(:initializing)
@@ -248,6 +251,64 @@ describe Ably::Modules::StateEmitter do
248
251
  subject.change_state :connecting, *arguments
249
252
  end
250
253
  end
254
+
255
+ context 'with blocks that raise exceptions' do
256
+ let(:success_block) do
257
+ proc { raise 'Success exception' }
258
+ end
259
+
260
+ let(:failure_block) do
261
+ proc { raise 'Failure exception' }
262
+ end
263
+
264
+ let(:target_state) { :connected }
265
+
266
+ before do
267
+ subject.once_or_if target_state, else: failure_block, &success_block
268
+ end
269
+
270
+ context 'success block' do
271
+ it 'catches exceptions in the provided block, logs the error and continues' do
272
+ expect(subject.logger).to receive(:error).with(/Success exception/)
273
+ subject.change_state target_state
274
+ end
275
+ end
276
+
277
+ context 'failure block' do
278
+ it 'catches exceptions in the provided block, logs the error and continues' do
279
+ expect(subject.logger).to receive(:error).with(/Failure exception/)
280
+ subject.change_state :connecting
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ context '#unsafe_once_or_if', :api_private do
287
+ let(:target_state) { :connected }
288
+
289
+ let(:success_block) do
290
+ proc { raise 'Success exception' }
291
+ end
292
+
293
+ let(:failure_block) do
294
+ proc { raise 'Failure exception' }
295
+ end
296
+
297
+ before do
298
+ subject.unsafe_once_or_if target_state, else: failure_block, &success_block
299
+ end
300
+
301
+ context 'success block' do
302
+ it 'catches exceptions in the provided block, logs the error and continues' do
303
+ expect { subject.change_state target_state }.to raise_error(/Success exception/)
304
+ end
305
+ end
306
+
307
+ context 'failure block' do
308
+ it 'catches exceptions in the provided block, logs the error and continues' do
309
+ expect { subject.change_state :connecting }.to raise_error(/Failure exception/)
310
+ end
311
+ end
251
312
  end
252
313
 
253
314
  context '#once_state_changed', :api_private do
@@ -279,5 +340,18 @@ describe Ably::Modules::StateEmitter do
279
340
  expect(block_calls.count).to eql(1)
280
341
  expect(block_calls.first).to contain_exactly(1, 2)
281
342
  end
343
+
344
+ it 'catches exceptions in the provided block, logs the error and continues' do
345
+ subject.once_state_changed { raise 'Intentional exception' }
346
+ expect(subject.logger).to receive(:error).with(/Intentional exception/)
347
+ subject.change_state :connected
348
+ end
349
+ end
350
+
351
+ context '#unsafe_once_state_changed', :api_private do
352
+ it 'does not catch exceptions in the provided block' do
353
+ subject.unsafe_once_state_changed { raise 'Intentional exception' }
354
+ expect { subject.change_state :connected }.to raise_error(/Intentional exception/)
355
+ end
282
356
  end
283
357
  end
@@ -133,7 +133,7 @@ describe Ably::Realtime::Channel do
133
133
  Ably::Models::Message.new({
134
134
  'name' => 'test',
135
135
  'data' => 'payload'
136
- }, instance_double('Ably::Models::ProtocolMessage'))
136
+ }, protocol_message: instance_double('Ably::Models::ProtocolMessage'))
137
137
  end
138
138
  let(:msgbus) { subject.__incoming_msgbus__ }
139
139
 
@@ -152,20 +152,45 @@ describe Ably::Realtime::Channel do
152
152
 
153
153
  context 'subscriptions' do
154
154
  let(:message_history) { Hash.new { |hash, key| hash[key] = 0 } }
155
- let(:event_name) { 'click' }
156
- let(:message) { instance_double('Ably::Models::Message', name: event_name, encode: nil, decode: nil) }
155
+ let(:click_event) { 'click' }
156
+ let(:click_message) { instance_double('Ably::Models::Message', name: click_event, encode: nil, decode: nil) }
157
+ let(:focus_event) { 'focus' }
158
+ let(:focus_message) { instance_double('Ably::Models::Message', name: focus_event, encode: nil, decode: nil) }
159
+ let(:blur_message) { instance_double('Ably::Models::Message', name: 'blur', encode: nil, decode: nil) }
157
160
 
158
161
  context '#subscribe' do
159
- specify 'to all events' do
162
+ specify 'without a block raises an invalid ArgumentError' do
163
+ expect { subject.subscribe }.to raise_error ArgumentError
164
+ end
165
+
166
+ specify 'with no event name specified subscribes the provided block to all events' do
160
167
  subject.subscribe { |message| message_history[:received] += 1}
161
- subject.__incoming_msgbus__.publish(:message, message)
168
+ subject.__incoming_msgbus__.publish(:message, click_message)
169
+ expect(message_history[:received]).to eql(1)
170
+ end
171
+
172
+ specify 'with a single event name subscribes that block to matching events' do
173
+ subject.subscribe(click_event) { |message| message_history[:received] += 1 }
174
+ subject.subscribe('non_match_move') { |message| message_history[:received] += 1 }
175
+ subject.__incoming_msgbus__.publish(:message, click_message)
176
+ expect(message_history[:received]).to eql(1)
177
+ end
178
+
179
+ specify 'with a multiple event name arguments subscribes that block to all of those event names' do
180
+ subject.subscribe(focus_event, click_event) { |message| message_history[:received] += 1 }
181
+ subject.__incoming_msgbus__.publish(:message, click_message)
162
182
  expect(message_history[:received]).to eql(1)
183
+ subject.__incoming_msgbus__.publish(:message, focus_message)
184
+ expect(message_history[:received]).to eql(2)
185
+
186
+ # Blur does not match subscribed focus & click events
187
+ subject.__incoming_msgbus__.publish(:message, blur_message)
188
+ expect(message_history[:received]).to eql(2)
163
189
  end
164
190
 
165
- specify 'to specific events' do
166
- subject.subscribe(event_name) { |message| message_history[:received] += 1 }
167
- subject.subscribe('move') { |message| message_history[:received] += 1 }
168
- subject.__incoming_msgbus__.publish(:message, message)
191
+ specify 'with a multiple duplicate event name arguments subscribes that block to all of those unique event names once' do
192
+ subject.subscribe(click_event, click_event) { |message| message_history[:received] += 1 }
193
+ subject.__incoming_msgbus__.publish(:message, click_message)
169
194
  expect(message_history[:received]).to eql(1)
170
195
  end
171
196
  end
@@ -175,30 +200,36 @@ describe Ably::Realtime::Channel do
175
200
  Proc.new { |message| message_history[:received] += 1 }
176
201
  end
177
202
  before do
178
- subject.subscribe(event_name, &callback)
203
+ subject.subscribe(click_event, &callback)
179
204
  end
180
205
 
181
- specify 'to all events' do
206
+ specify 'with no event name specified unsubscribes that block from all events' do
182
207
  subject.unsubscribe &callback
183
- subject.__incoming_msgbus__.publish(:message, message)
208
+ subject.__incoming_msgbus__.publish(:message, click_message)
209
+ expect(message_history[:received]).to eql(0)
210
+ end
211
+
212
+ specify 'with a single event name argument unsubscribes the provided block with the matching event name' do
213
+ subject.unsubscribe click_event, &callback
214
+ subject.__incoming_msgbus__.publish(:message, click_message)
184
215
  expect(message_history[:received]).to eql(0)
185
216
  end
186
217
 
187
- specify 'to specific events' do
188
- subject.unsubscribe event_name, &callback
189
- subject.__incoming_msgbus__.publish(:message, message)
218
+ specify 'with multiple event name arguments unsubscribes each of those matching event names with the provided block' do
219
+ subject.unsubscribe focus_event, click_event, &callback
220
+ subject.__incoming_msgbus__.publish(:message, click_message)
190
221
  expect(message_history[:received]).to eql(0)
191
222
  end
192
223
 
193
- specify 'to specific non-matching events' do
224
+ specify 'with a non-matching event name argument has no effect' do
194
225
  subject.unsubscribe 'move', &callback
195
- subject.__incoming_msgbus__.publish(:message, message)
226
+ subject.__incoming_msgbus__.publish(:message, click_message)
196
227
  expect(message_history[:received]).to eql(1)
197
228
  end
198
229
 
199
- specify 'all callbacks by not providing a callback' do
200
- subject.unsubscribe event_name
201
- subject.__incoming_msgbus__.publish(:message, message)
230
+ specify 'with no block argument unsubscribes all blocks for the event name argument' do
231
+ subject.unsubscribe click_event
232
+ subject.__incoming_msgbus__.publish(:message, click_message)
202
233
  expect(message_history[:received]).to eql(0)
203
234
  end
204
235
  end