ably 0.8.15 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
@@ -146,4 +146,79 @@ describe Ably::Models::TokenDetails do
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ context 'from_json (#TD7)' do
151
+ let(:issued_time) { Time.now }
152
+ let(:expires_time) { Time.now + 24*60*60 }
153
+ let(:capabilities) { { '*' => ['publish'] } }
154
+
155
+ context 'with Ruby idiomatic Hash object' do
156
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
157
+
158
+ let(:token_details_object) do
159
+ {
160
+ token: 'val1',
161
+ key_name: 'val2',
162
+ issued: issued_time.to_i * 1000,
163
+ expires: expires_time.to_i * 1000,
164
+ capability: capabilities,
165
+ client_id: 'val3'
166
+ }
167
+ end
168
+
169
+ it 'returns a valid TokenDetails object' do
170
+ expect(subject.token).to eql('val1')
171
+ expect(subject.key_name).to eql('val2')
172
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
173
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
174
+ expect(subject.capability).to eql(capabilities)
175
+ expect(subject.client_id).to eql('val3')
176
+ end
177
+ end
178
+
179
+ context 'with JSON-like object' do
180
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
181
+
182
+ let(:token_details_object) do
183
+ {
184
+ 'keyName' => 'val2',
185
+ 'issued' => issued_time.to_i * 1000,
186
+ 'expires' => expires_time.to_i * 1000,
187
+ 'capability' => JSON.dump(capabilities),
188
+ 'clientId' => 'val3'
189
+ }
190
+ end
191
+
192
+ it 'returns a valid TokenDetails object' do
193
+ expect(subject.token).to be_nil
194
+ expect(subject.key_name).to eql('val2')
195
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
196
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
197
+ expect(subject.capability).to eql(capabilities)
198
+ expect(subject.client_id).to eql('val3')
199
+ end
200
+ end
201
+
202
+ context 'with JSON string' do
203
+ subject { Ably::Models::TokenDetails.from_json(JSON.dump(token_details_object)) }
204
+
205
+ let(:token_details_object) do
206
+ {
207
+ 'keyName' => 'val2',
208
+ 'issued' => issued_time.to_i * 1000,
209
+ 'expires' => expires_time.to_i * 1000,
210
+ 'clientId' => 'val3'
211
+ }
212
+ end
213
+
214
+ it 'returns a valid TokenDetails object' do
215
+ expect(subject.token).to be_nil
216
+ expect(subject.key_name).to eql('val2')
217
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
218
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
219
+ expect(subject.capability).to be_nil
220
+ expect(subject.client_id).to eql('val3')
221
+ end
222
+ end
223
+ end
149
224
  end
@@ -107,4 +107,78 @@ describe Ably::Models::TokenRequest do
107
107
  end
108
108
  end
109
109
  end
110
+
111
+ context 'from_json (#TE6)' do
112
+ let(:timestamp) { Time.now }
113
+ let(:capabilities) { { '*' => ['publish'] } }
114
+ let(:ttl_seconds) { 60 * 1000 }
115
+
116
+ context 'with Ruby idiomatic Hash object' do
117
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
118
+
119
+ let(:token_request_object) do
120
+ {
121
+ nonce: 'val1',
122
+ key_name: 'val2',
123
+ ttl: ttl_seconds * 1000,
124
+ timestamp: timestamp.to_i * 1000,
125
+ capability: capabilities,
126
+ client_id: 'val3'
127
+ }
128
+ end
129
+
130
+ it 'returns a valid TokenRequest object' do
131
+ expect(subject.nonce).to eql('val1')
132
+ expect(subject.key_name).to eql('val2')
133
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
134
+ expect(subject.ttl).to eql(ttl_seconds)
135
+ expect(subject.capability).to eql(capabilities)
136
+ expect(subject.client_id).to eql('val3')
137
+ end
138
+ end
139
+
140
+ context 'with JSON-like object' do
141
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
142
+
143
+ let(:token_request_object) do
144
+ {
145
+ 'keyName' => 'val2',
146
+ 'ttl' => ttl_seconds * 1000,
147
+ 'timestamp' => timestamp.to_i * 1000,
148
+ 'clientId' => 'val3'
149
+ }
150
+ end
151
+
152
+ it 'returns a valid TokenRequest object' do
153
+ expect { subject.nonce }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
154
+ expect(subject.key_name).to eql('val2')
155
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
156
+ expect(subject.ttl).to eql(ttl_seconds)
157
+ expect { subject.capability }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
158
+ expect(subject.client_id).to eql('val3')
159
+ end
160
+ end
161
+
162
+ context 'with JSON string' do
163
+ subject { Ably::Models::TokenRequest.from_json(JSON.dump(token_request_object)) }
164
+
165
+ let(:token_request_object) do
166
+ {
167
+ 'nonce' => 'val1',
168
+ 'ttl' => ttl_seconds * 1000,
169
+ 'capability' => JSON.dump(capabilities),
170
+ 'clientId' => 'val3'
171
+ }
172
+ end
173
+
174
+ it 'returns a valid TokenRequest object' do
175
+ expect(subject.nonce).to eql('val1')
176
+ expect { subject.key_name }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
177
+ expect { subject.timestamp }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
178
+ expect(subject.ttl).to eql(ttl_seconds)
179
+ expect(subject.capability).to eql(capabilities)
180
+ expect(subject.client_id).to eql('val3')
181
+ end
182
+ end
183
+ end
110
184
  end
@@ -59,7 +59,8 @@ describe Ably::Modules::AsyncWrapper, :api_private do
59
59
  subject.operation do |result|
60
60
  raise 'Intentional exception'
61
61
  end
62
- expect(subject.logger).to receive(:error).with(/Intentional exception/) do
62
+ expect(subject.logger).to receive(:error) do |*args, &block|
63
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
63
64
  stop_reactor
64
65
  end
65
66
  end
@@ -41,6 +41,41 @@ describe Ably::Modules::Enum, :api_private do
41
41
  end
42
42
  end
43
43
 
44
+ context 'using include? to compare Enum values' do
45
+ subject { enum }
46
+
47
+ it 'allows same type comparison' do
48
+ expect([subject.ValueZero].include?(subject.ValueZero)).to eql(true)
49
+ end
50
+
51
+ it 'allows different type comparison 1' do
52
+ expect([subject.ValueZero].include?(:value_zero)).to eql(true)
53
+ end
54
+
55
+ it 'allows different type comparison 2' do
56
+ skip 'Unless we monkeypath Symbols, the == operator is never invoked'
57
+ expect([:value_zero].include?(subject.ValueZero)).to eql(true)
58
+ end
59
+
60
+ context '#match_any? replacement for include?' do
61
+ it 'matches any value in the arguments provided' do
62
+ expect(subject.ValueZero.match_any?(:value_foo, :value_zero)).to eql(true)
63
+ end
64
+
65
+ it 'returns false if there are no matches in any value in the arguments provided' do
66
+ expect(subject.ValueZero.match_any?(:value_x, :value_y)).to eql(false)
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'class method #to_sym_arr' do
72
+ subject { enum }
73
+
74
+ it 'returns all keys as symbols' do
75
+ expect(enum.to_sym_arr).to contain_exactly(:value_zero, :value_1, :value_snake_case_2, :sentence_case)
76
+ end
77
+ end
78
+
44
79
  context 'defined Enum from Array class' do
45
80
  subject { enum }
46
81
 
@@ -212,6 +247,40 @@ describe Ably::Modules::Enum, :api_private do
212
247
  end
213
248
  end
214
249
 
250
+ context 'two similar Enums with shared symbol values' do
251
+ class ExampleEnumOne
252
+ extend Ably::Modules::Enum
253
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :pear, :orange, :litchi, :apple)
254
+ end
255
+
256
+ class ExampleEnumTwo
257
+ extend Ably::Modules::Enum
258
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :pear, :grape, :apple)
259
+ end
260
+
261
+ let(:enum_one) { ExampleEnumOne::ENUMEXAMPLE }
262
+ let(:enum_two) { ExampleEnumTwo::ENUMEXAMPLE }
263
+
264
+ it 'provides compatability for the equivalent symbol values' do
265
+ expect(enum_one.Pear).to eq(enum_two.Pear)
266
+ expect(enum_two.Pear).to eq(enum_one.Pear)
267
+ expect(enum_one.Apple).to eq(enum_two.Apple)
268
+ expect(enum_two.Apple).to eq(enum_one.Apple)
269
+ end
270
+
271
+ it 'does not consider different symbol values the same' do
272
+ expect(enum_one.Orange).to_not eq(enum_two.Grape)
273
+ end
274
+
275
+ it 'matches symbols when used with a converter method' do
276
+ expect(ExampleEnumOne::ENUMEXAMPLE(enum_two.Pear)).to eq(:pear)
277
+ end
278
+
279
+ it 'fails to match when using an incompatible method with a converter method' do
280
+ expect { ExampleEnumOne::ENUMEXAMPLE(enum_two.Grape) }.to raise_error KeyError
281
+ end
282
+ end
283
+
215
284
  context 'Enum instance' do
216
285
  context '#==' do
217
286
  subject { enum.get(:value_snake_case_2) }
@@ -83,13 +83,13 @@ describe Ably::Modules::EventEmitter do
83
83
  allow(obj).to receive(:received_message)
84
84
  end
85
85
 
86
- it 'is unaffected and processes the prior event callbacks once' do
86
+ it 'is unaffected and processes the prior event callbacks once (#RTE6b)' do
87
87
  expect(obj).to receive(:received_message).with(msg).twice
88
88
  expect(obj).to_not receive(:received_message_from_new_callbacks).with(msg)
89
89
  subject.emit :message, msg
90
90
  end
91
91
 
92
- it 'adds them for the next emitted event' do
92
+ it 'adds them for the next emitted event (#RTE6b)' do
93
93
  expect(obj).to receive(:received_message_from_new_callbacks).with(msg).twice
94
94
 
95
95
  # New callbacks are added in this emit
@@ -110,12 +110,12 @@ describe Ably::Modules::EventEmitter do
110
110
  end
111
111
  end
112
112
 
113
- it 'is unaffected and processes the prior event callbacks once' do
113
+ it 'is unaffected and processes the prior event callbacks once (#RTE6b)' do
114
114
  expect(obj).to receive(:received_message).with(msg).twice
115
115
  subject.emit :message, msg
116
116
  end
117
117
 
118
- it 'removes them for the next emitted event' do
118
+ it 'removes them for the next emitted event (#RTE6b)' do
119
119
  expect(obj).to receive(:received_message).with(msg).twice
120
120
 
121
121
  # Callbacks are removed in this emit
@@ -127,7 +127,7 @@ describe Ably::Modules::EventEmitter do
127
127
  end
128
128
  end
129
129
 
130
- context '#on' do
130
+ context '#on (#RTE3)' do
131
131
  context 'with event specified' do
132
132
  it 'calls the block every time an event is emitted only' do
133
133
  block_called = 0
@@ -137,7 +137,9 @@ describe Ably::Modules::EventEmitter do
137
137
  end
138
138
 
139
139
  it 'catches exceptions in the provided block, logs the error and continues' do
140
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
140
+ expect(subject.logger).to receive(:error) do |*args, &block|
141
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
142
+ end
141
143
  subject.on(:event) { raise 'Intentional exception' }
142
144
  subject.emit :event
143
145
  end
@@ -152,7 +154,9 @@ describe Ably::Modules::EventEmitter do
152
154
  end
153
155
 
154
156
  it 'catches exceptions in the provided block, logs the error and continues' do
155
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
157
+ expect(subject.logger).to receive(:error) do |*args, &block|
158
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
159
+ end
156
160
  subject.on { raise 'Intentional exception' }
157
161
  subject.emit :event
158
162
  end
@@ -173,7 +177,7 @@ describe Ably::Modules::EventEmitter do
173
177
  end
174
178
  end
175
179
 
176
- context '#once' do
180
+ context '#once (#RTE4)' do
177
181
  context 'with event specified' do
178
182
  it 'calls the block the first time an event is emitted only' do
179
183
  block_called = 0
@@ -191,7 +195,9 @@ describe Ably::Modules::EventEmitter do
191
195
  end
192
196
 
193
197
  it 'catches exceptions in the provided block, logs the error and continues' do
194
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
198
+ expect(subject.logger).to receive(:error) do |*args, &block|
199
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
200
+ end
195
201
  subject.once(:event) { raise 'Intentional exception' }
196
202
  subject.emit :event
197
203
  end
@@ -214,7 +220,9 @@ describe Ably::Modules::EventEmitter do
214
220
  end
215
221
 
216
222
  it 'catches exceptions in the provided block, logs the error and continues' do
217
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
223
+ expect(subject.logger).to receive(:error) do |*args, &block|
224
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
225
+ end
218
226
  subject.once { raise 'Intentional exception' }
219
227
  subject.emit :event
220
228
  end
@@ -248,12 +256,12 @@ describe Ably::Modules::EventEmitter do
248
256
  end
249
257
 
250
258
  context 'with event names as arguments' do
251
- it 'deletes matching callbacks' do
259
+ it 'deletes matching callbacks when a block is provided' do
252
260
  expect(obj).to_not receive(:received_message).with(msg)
253
261
  subject.off(:message, &callback)
254
262
  end
255
263
 
256
- it 'deletes all callbacks if not block given' do
264
+ it 'deletes all matching callbacks when a block is not provided' do
257
265
  expect(obj).to_not receive(:received_message).with(msg)
258
266
  subject.off(:message)
259
267
  end
@@ -277,18 +285,137 @@ describe Ably::Modules::EventEmitter do
277
285
  end
278
286
  end
279
287
 
280
- it 'removes handler added with no event specified' do
281
- subject.on(&callback)
282
- expect(obj).to_not receive(:received_message).with(msg)
283
- subject.off(&callback)
284
- subject.emit :message, msg
288
+ context 'when on callback is configured for all events' do
289
+ before do
290
+ subject.on(&callback)
291
+ end
292
+
293
+ after do
294
+ subject.emit :message, msg
295
+ end
296
+
297
+ context 'with event names as arguments' do
298
+ it 'does not remove the all events callback when a block is provided' do
299
+ expect(obj).to receive(:received_message).with(msg)
300
+ subject.off(:message, &callback)
301
+ end
302
+
303
+ it 'does not remove the all events callback when a block is not provided' do
304
+ expect(obj).to receive(:received_message).with(msg)
305
+ subject.off(:message)
306
+ end
307
+
308
+ it 'does not remove the all events callback when the block does not match' do
309
+ expect(obj).to receive(:received_message).with(msg)
310
+ subject.off(:message) { true }
311
+ end
312
+ end
313
+
314
+ context 'without any event names' do
315
+ it 'deletes all matching callbacks' do
316
+ expect(obj).to_not receive(:received_message).with(msg)
317
+ subject.off(&callback)
318
+ end
319
+
320
+ it 'deletes all callbacks if not block given' do
321
+ expect(obj).to_not receive(:received_message).with(msg)
322
+ subject.off
323
+ end
324
+ end
285
325
  end
286
326
 
287
- it 'leaves handler when event specified' do
288
- subject.on(&callback)
289
- expect(obj).to receive(:received_message).with(msg)
290
- subject.off(:foo, &callback)
291
- subject.emit :message, msg
327
+ context 'with unsafe_on subscribers' do
328
+ before do
329
+ subject.unsafe_on(&callback)
330
+ end
331
+
332
+ after do
333
+ subject.emit :message, msg
334
+ end
335
+
336
+ it 'does not deregister them' do
337
+ expect(obj).to receive(:received_message).with(msg)
338
+ subject.off
339
+ end
340
+ end
341
+
342
+ context 'with unsafe_once subscribers' do
343
+ before do
344
+ subject.unsafe_once(&callback)
345
+ end
346
+
347
+ after do
348
+ subject.emit :message, msg
349
+ end
350
+
351
+ it 'does not deregister them' do
352
+ expect(obj).to receive(:received_message).with(msg)
353
+ subject.off
354
+ end
355
+ end
356
+ end
357
+
358
+ context '#unsafe_off' do
359
+ let(:callback) { Proc.new { |msg| obj.received_message msg } }
360
+
361
+ context 'with unsafe_on subscribers' do
362
+ before do
363
+ subject.unsafe_on(&callback)
364
+ end
365
+
366
+ after do
367
+ subject.emit :message, msg
368
+ end
369
+
370
+ it 'deregisters them' do
371
+ expect(obj).to_not receive(:received_message).with(msg)
372
+ subject.unsafe_off
373
+ end
374
+ end
375
+
376
+ context 'with unsafe_once subscribers' do
377
+ before do
378
+ subject.unsafe_once(&callback)
379
+ end
380
+
381
+ after do
382
+ subject.emit :message, msg
383
+ end
384
+
385
+ it 'deregister them' do
386
+ expect(obj).to_not receive(:received_message).with(msg)
387
+ subject.unsafe_off
388
+ end
389
+ end
390
+
391
+ context 'with on subscribers' do
392
+ before do
393
+ subject.on(&callback)
394
+ end
395
+
396
+ after do
397
+ subject.emit :message, msg
398
+ end
399
+
400
+ it 'does not deregister them' do
401
+ expect(obj).to receive(:received_message).with(msg)
402
+ subject.unsafe_off
403
+ end
404
+ end
405
+
406
+ context 'with once subscribers' do
407
+ before do
408
+ subject.once(&callback)
409
+ end
410
+
411
+ after do
412
+ subject.emit :message, msg
413
+ end
414
+
415
+ it 'does not deregister them' do
416
+ expect(obj).to receive(:received_message).with(msg)
417
+ subject.unsafe_off
418
+ end
292
419
  end
293
420
  end
294
421
  end