ably-rest 0.7.3 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
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