ably 0.8.15 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -4
- data/CHANGELOG.md +6 -2
- data/README.md +5 -1
- data/SPEC.md +1473 -852
- data/ably.gemspec +11 -8
- data/lib/ably/auth.rb +90 -53
- data/lib/ably/exceptions.rb +37 -8
- data/lib/ably/logger.rb +10 -1
- data/lib/ably/models/auth_details.rb +42 -0
- data/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/ably/models/connection_details.rb +6 -3
- data/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +17 -1
- data/lib/ably/models/message_encoders/base.rb +103 -82
- data/lib/ably/models/message_encoders/base64.rb +1 -1
- data/lib/ably/models/presence_message.rb +16 -1
- data/lib/ably/models/protocol_message.rb +20 -3
- data/lib/ably/models/token_details.rb +11 -1
- data/lib/ably/models/token_request.rb +16 -6
- data/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/ably/modules/encodeable.rb +51 -12
- data/lib/ably/modules/enum.rb +17 -7
- data/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/ably/modules/model_common.rb +13 -21
- data/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/ably/modules/state_machine.rb +2 -4
- data/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/ably/realtime.rb +2 -0
- data/lib/ably/realtime/auth.rb +102 -42
- data/lib/ably/realtime/channel.rb +68 -26
- data/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/ably/realtime/client.rb +18 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/ably/realtime/connection.rb +108 -49
- data/lib/ably/realtime/connection/connection_manager.rb +167 -61
- data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/ably/realtime/presence.rb +70 -45
- data/lib/ably/realtime/presence/members_map.rb +201 -36
- data/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +5 -5
- data/lib/ably/rest/client.rb +31 -27
- data/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/ably/rest/presence.rb +2 -2
- data/lib/ably/util/pub_sub.rb +1 -1
- data/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/ably/version.rb +2 -2
- data/spec/acceptance/realtime/auth_spec.rb +470 -111
- data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/spec/acceptance/realtime/channel_spec.rb +1017 -168
- data/spec/acceptance/realtime/client_spec.rb +6 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/spec/acceptance/realtime/connection_spec.rb +424 -105
- data/spec/acceptance/realtime/message_spec.rb +52 -23
- data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/spec/acceptance/realtime/presence_spec.rb +1110 -96
- data/spec/acceptance/rest/auth_spec.rb +222 -59
- data/spec/acceptance/rest/base_spec.rb +1 -1
- data/spec/acceptance/rest/channel_spec.rb +1 -2
- data/spec/acceptance/rest/client_spec.rb +104 -48
- data/spec/acceptance/rest/message_spec.rb +42 -15
- data/spec/acceptance/rest/presence_spec.rb +4 -11
- data/spec/rspec_config.rb +2 -1
- data/spec/shared/client_initializer_behaviour.rb +2 -2
- data/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/debug_failure_helper.rb +20 -4
- data/spec/support/event_machine_helper.rb +32 -1
- data/spec/unit/auth_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +28 -2
- data/spec/unit/models/auth_details_spec.rb +49 -0
- data/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/spec/unit/models/connection_details_spec.rb +12 -1
- data/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
- data/spec/unit/models/message_spec.rb +153 -0
- data/spec/unit/models/presence_message_spec.rb +192 -0
- data/spec/unit/models/protocol_message_spec.rb +64 -6
- data/spec/unit/models/token_details_spec.rb +75 -0
- data/spec/unit/models/token_request_spec.rb +74 -0
- data/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/spec/unit/modules/enum_spec.rb +69 -0
- data/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/spec/unit/realtime/client_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +8 -5
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/spec/unit/realtime/presence_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +1 -1
- data/spec/unit/util/crypto_spec.rb +3 -3
- metadata +22 -19
@@ -75,6 +75,40 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
context 'with supported extra payload content type (#RTL6h, #RSL6a2)' do
|
79
|
+
def publish_and_check_extras(extras)
|
80
|
+
channel.attach
|
81
|
+
channel.publish 'event', {}, extras: extras
|
82
|
+
channel.subscribe do |message|
|
83
|
+
expect(message.extras).to eql(extras)
|
84
|
+
stop_reactor
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'JSON Object (Hash)' do
|
89
|
+
let(:data) { { 'push' => { 'title' => 'Testing' } } }
|
90
|
+
|
91
|
+
it 'is encoded and decoded to the same hash' do
|
92
|
+
publish_and_check_extras data
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'JSON Array' do
|
97
|
+
let(:data) { { 'push' => [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } }
|
98
|
+
|
99
|
+
it 'is encoded and decoded to the same Array' do
|
100
|
+
publish_and_check_extras data
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'nil' do
|
105
|
+
it 'is encoded and decoded to the same Array' do
|
106
|
+
channel.publish 'event', {}, extras: nil
|
107
|
+
publish_and_check_extras nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
78
112
|
context 'with unsupported data payload content type' do
|
79
113
|
context 'Integer' do
|
80
114
|
let(:data) { 1 }
|
@@ -327,8 +361,9 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
327
361
|
end
|
328
362
|
2.times { |i| EventMachine.add_timer(i.to_f / 5) { channel.publish('event', 'data') } }
|
329
363
|
|
330
|
-
|
331
|
-
expect(
|
364
|
+
expect(client.logger).to receive(:error) do |*args, &block|
|
365
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/duplicate/)
|
366
|
+
|
332
367
|
EventMachine.add_timer(0.5) do
|
333
368
|
expect(messages_received.count).to eql(2)
|
334
369
|
stop_reactor
|
@@ -374,7 +409,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
374
409
|
|
375
410
|
let(:encrypted_channel) { client.channel(channel_name, cipher: cipher_options) }
|
376
411
|
|
377
|
-
it 'encrypts message automatically before they are pushed to the server' do
|
412
|
+
it 'encrypts message automatically before they are pushed to the server (#RTL7d)' do
|
378
413
|
encrypted_channel.__incoming_msgbus__.unsubscribe # remove all subscribe callbacks that could decrypt the message
|
379
414
|
|
380
415
|
encrypted_channel.__incoming_msgbus__.subscribe(:message) do |message|
|
@@ -392,7 +427,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
392
427
|
encrypted_channel.publish 'example', encoded_data_decoded
|
393
428
|
end
|
394
429
|
|
395
|
-
it 'sends and receives messages that are encrypted & decrypted by the Ably library' do
|
430
|
+
it 'sends and receives messages that are encrypted & decrypted by the Ably library (#RTL7d)' do
|
396
431
|
encrypted_channel.publish 'example', encoded_data_decoded
|
397
432
|
encrypted_channel.subscribe do |message|
|
398
433
|
expect(message.data).to eql(encoded_data_decoded)
|
@@ -413,12 +448,12 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
413
448
|
end
|
414
449
|
end
|
415
450
|
|
416
|
-
context 'with AES-128-CBC using crypto-data-128.json fixtures' do
|
451
|
+
context 'with AES-128-CBC using crypto-data-128.json fixtures (#RTL7d)' do
|
417
452
|
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
|
418
453
|
add_tests_for_data data
|
419
454
|
end
|
420
455
|
|
421
|
-
context 'with AES-256-CBC using crypto-data-256.json fixtures' do
|
456
|
+
context 'with AES-256-CBC using crypto-data-256.json fixtures (#RTL7d)' do
|
422
457
|
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
|
423
458
|
add_tests_for_data data
|
424
459
|
end
|
@@ -521,7 +556,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
521
556
|
|
522
557
|
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
523
558
|
|
524
|
-
it 'delivers the message but still encrypted with a value in the #encoding attribute' do
|
559
|
+
it 'delivers the message but still encrypted with a value in the #encoding attribute (#RTL7e)' do
|
525
560
|
unencrypted_channel_client2.attach do
|
526
561
|
encrypted_channel_client1.publish 'example', payload
|
527
562
|
unencrypted_channel_client2.subscribe do |message|
|
@@ -532,15 +567,13 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
532
567
|
end
|
533
568
|
end
|
534
569
|
|
535
|
-
it '
|
570
|
+
it 'logs a Cipher error (#RTL7e)' do
|
536
571
|
unencrypted_channel_client2.attach do
|
537
|
-
|
538
|
-
|
539
|
-
expect(error).to be_a(Ably::Exceptions::CipherError)
|
540
|
-
expect(error.code).to eql(92001)
|
541
|
-
expect(error.message).to match(/Message cannot be decrypted/)
|
572
|
+
expect(other_client.logger).to receive(:error) do |*args, &block|
|
573
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/Message cannot be decrypted/)
|
542
574
|
stop_reactor
|
543
575
|
end
|
576
|
+
encrypted_channel_client1.publish 'example', payload
|
544
577
|
end
|
545
578
|
end
|
546
579
|
end
|
@@ -554,7 +587,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
554
587
|
|
555
588
|
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
556
589
|
|
557
|
-
it 'delivers the message but still encrypted with the cipher detials in the #encoding attribute' do
|
590
|
+
it 'delivers the message but still encrypted with the cipher detials in the #encoding attribute (#RTL7e)' do
|
558
591
|
encrypted_channel_client1.publish 'example', payload
|
559
592
|
encrypted_channel_client2.subscribe do |message|
|
560
593
|
expect(message.data).to_not eql(payload)
|
@@ -563,13 +596,11 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
563
596
|
end
|
564
597
|
end
|
565
598
|
|
566
|
-
it 'emits a Cipher error on the channel' do
|
599
|
+
it 'emits a Cipher error on the channel (#RTL7e)' do
|
567
600
|
encrypted_channel_client2.attach do
|
568
601
|
encrypted_channel_client1.publish 'example', payload
|
569
|
-
|
570
|
-
expect(
|
571
|
-
expect(error.code).to eql(92002)
|
572
|
-
expect(error.message).to match(/Cipher algorithm [\w-]+ does not match/)
|
602
|
+
expect(other_client.logger).to receive(:error) do |*args, &block|
|
603
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/Cipher algorithm [\w-]+ does not match/)
|
573
604
|
stop_reactor
|
574
605
|
end
|
575
606
|
end
|
@@ -599,10 +630,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
599
630
|
it 'emits a Cipher error on the channel' do
|
600
631
|
encrypted_channel_client2.attach do
|
601
632
|
encrypted_channel_client1.publish 'example', payload
|
602
|
-
|
603
|
-
expect(
|
604
|
-
expect(error.code).to eql(92003)
|
605
|
-
expect(error.message).to match(/CipherError decrypting data/)
|
633
|
+
expect(other_client.logger).to receive(:error) do |*args, &block|
|
634
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/CipherError decrypting data/)
|
606
635
|
stop_reactor
|
607
636
|
end
|
608
637
|
end
|
@@ -94,9 +94,11 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
it '
|
98
|
-
|
99
|
-
|
97
|
+
it 'fails with an exception unless state is attached' do
|
98
|
+
presence_client_one.history(until_attach: true).errback do |error|
|
99
|
+
expect(error.message).to match(/not attached/)
|
100
|
+
stop_reactor
|
101
|
+
end
|
100
102
|
end
|
101
103
|
end
|
102
104
|
end
|
@@ -42,11 +42,18 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
42
42
|
|
43
43
|
def setup_test(method_name, args, options)
|
44
44
|
if options[:enter_first]
|
45
|
-
|
45
|
+
acked = false
|
46
|
+
received = false
|
47
|
+
presence_client_one.public_send(method_name.to_s.gsub(/leave|update/, 'enter'), args) do
|
48
|
+
acked = true
|
49
|
+
yield if acked & received
|
50
|
+
end
|
51
|
+
presence_client_one.subscribe do |presence_message|
|
52
|
+
expect(presence_message.action).to eq(:enter)
|
46
53
|
presence_client_one.unsubscribe
|
47
|
-
|
54
|
+
received = true
|
55
|
+
yield if acked & received
|
48
56
|
end
|
49
|
-
presence_client_one.public_send(method_name.to_s.gsub(/leave|update/, 'enter'), args)
|
50
57
|
else
|
51
58
|
yield
|
52
59
|
end
|
@@ -58,8 +65,30 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
58
65
|
channel_client_one.attach do
|
59
66
|
channel_client_one.transition_state_machine :detaching
|
60
67
|
channel_client_one.once(:detached) do
|
61
|
-
|
62
|
-
|
68
|
+
presence_client_one.public_send(method_name, args).tap do |deferrable|
|
69
|
+
deferrable.callback { raise 'Get should not succeed' }
|
70
|
+
deferrable.errback do |error|
|
71
|
+
expect(error).to be_a(Ably::Exceptions::InvalidState)
|
72
|
+
expect(error.message).to match(/Operation is not allowed when channel is in STATE.Detached/)
|
73
|
+
stop_reactor
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'raise an exception if the channel becomes detached' do
|
82
|
+
setup_test(method_name, args, options) do
|
83
|
+
channel_client_one.attach do
|
84
|
+
channel_client_one.transition_state_machine :detaching
|
85
|
+
presence_client_one.public_send(method_name, args).tap do |deferrable|
|
86
|
+
deferrable.callback { raise 'Get should not succeed' }
|
87
|
+
deferrable.errback do |error|
|
88
|
+
expect(error).to be_a(Ably::Exceptions::InvalidState)
|
89
|
+
expect(error.message).to match(/Operation failed as channel transitioned to STATE.Detached/)
|
90
|
+
stop_reactor
|
91
|
+
end
|
63
92
|
end
|
64
93
|
end
|
65
94
|
end
|
@@ -70,8 +99,30 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
70
99
|
channel_client_one.attach do
|
71
100
|
channel_client_one.transition_state_machine :failed
|
72
101
|
expect(channel_client_one.state).to eq(:failed)
|
73
|
-
|
74
|
-
|
102
|
+
presence_client_one.public_send(method_name, args).tap do |deferrable|
|
103
|
+
deferrable.callback { raise 'Get should not succeed' }
|
104
|
+
deferrable.errback do |error|
|
105
|
+
expect(error).to be_a(Ably::Exceptions::InvalidState)
|
106
|
+
expect(error.message).to match(/Operation is not allowed when channel is in STATE.Failed/)
|
107
|
+
stop_reactor
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'raise an exception if the channel becomes failed' do
|
115
|
+
setup_test(method_name, args, options) do
|
116
|
+
channel_client_one.attach do
|
117
|
+
presence_client_one.public_send(method_name, args).tap do |deferrable|
|
118
|
+
deferrable.callback { raise 'Get should not succeed' }
|
119
|
+
deferrable.errback do |error|
|
120
|
+
expect(error).to be_a(Ably::Exceptions::MessageDeliveryFailed)
|
121
|
+
stop_reactor
|
122
|
+
end
|
123
|
+
end
|
124
|
+
channel_client_one.transition_state_machine :failed
|
125
|
+
expect(channel_client_one.state).to eq(:failed)
|
75
126
|
end
|
76
127
|
end
|
77
128
|
end
|
@@ -88,20 +139,24 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
88
139
|
let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id)) }
|
89
140
|
|
90
141
|
context 'and connection state initialized' do
|
91
|
-
it '
|
92
|
-
|
142
|
+
it 'fails the deferrable' do
|
143
|
+
presence_client_one.public_send(method_name, args).errback do |error|
|
144
|
+
expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
|
145
|
+
stop_reactor
|
146
|
+
end
|
93
147
|
expect(client_one.connection).to be_initialized
|
94
|
-
stop_reactor
|
95
148
|
end
|
96
149
|
end
|
97
150
|
|
98
151
|
context 'and connection state connecting' do
|
99
|
-
it '
|
152
|
+
it 'fails the deferrable' do
|
100
153
|
client_one.connect
|
101
154
|
EventMachine.next_tick do
|
102
|
-
|
155
|
+
presence_client_one.public_send(method_name, args).errback do |error|
|
156
|
+
expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
|
157
|
+
stop_reactor
|
158
|
+
end
|
103
159
|
expect(client_one.connection).to be_connecting
|
104
|
-
stop_reactor
|
105
160
|
end
|
106
161
|
end
|
107
162
|
end
|
@@ -109,12 +164,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
109
164
|
context 'and connection state disconnected' do
|
110
165
|
let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id, :log_level => :error)) }
|
111
166
|
|
112
|
-
it '
|
167
|
+
it 'fails the deferrable' do
|
113
168
|
client_one.connection.once(:connected) do
|
114
169
|
client_one.connection.once(:disconnected) do
|
115
|
-
|
170
|
+
presence_client_one.public_send(method_name, args).errback do |error|
|
171
|
+
expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
|
172
|
+
stop_reactor
|
173
|
+
end
|
116
174
|
expect(client_one.connection).to be_disconnected
|
117
|
-
stop_reactor
|
118
175
|
end
|
119
176
|
client_one.connection.transition_state_machine :disconnected
|
120
177
|
end
|
@@ -260,7 +317,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
260
317
|
|
261
318
|
it 'catches exceptions in the provided method block and logs them to the logger' do
|
262
319
|
setup_test(method_name, args, options) do
|
263
|
-
expect(presence_client_one.logger).to receive(:error)
|
320
|
+
expect(presence_client_one.logger).to receive(:error) do |*args, &block|
|
321
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
|
264
322
|
stop_reactor
|
265
323
|
end
|
266
324
|
presence_client_one.public_send(method_name, args) { raise 'Intentional exception' }
|
@@ -323,6 +381,13 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
323
381
|
stop_reactor
|
324
382
|
end
|
325
383
|
end
|
384
|
+
|
385
|
+
context 'and a client_id that is not a string type' do
|
386
|
+
it 'throws an exception' do
|
387
|
+
expect { presence_channel.public_send(method_name, 1) }.to raise_error Ably::Exceptions::IncompatibleClientId
|
388
|
+
stop_reactor
|
389
|
+
end
|
390
|
+
end
|
326
391
|
end
|
327
392
|
|
328
393
|
context ":#{method_name} when authenticated with a valid client_id" do
|
@@ -411,14 +476,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
411
476
|
end
|
412
477
|
|
413
478
|
context 'when attached (but not present) on a presence channel with an anonymous client (no client ID)' do
|
414
|
-
it 'maintains state as other clients enter and leave the channel' do
|
479
|
+
it 'maintains state as other clients enter and leave the channel (#RTP2e)' do
|
415
480
|
channel_anonymous_client.attach do
|
416
481
|
presence_anonymous_client.subscribe(:enter) do |presence_message|
|
417
482
|
expect(presence_message.client_id).to eql(client_one.client_id)
|
418
483
|
|
419
484
|
presence_anonymous_client.get do |members|
|
420
485
|
expect(members.first.client_id).to eql(client_one.client_id)
|
421
|
-
expect(members.first.action).to eq(:
|
486
|
+
expect(members.first.action).to eq(:present)
|
422
487
|
|
423
488
|
presence_anonymous_client.subscribe(:leave) do |leave_presence_message|
|
424
489
|
expect(leave_presence_message.client_id).to eql(client_one.client_id)
|
@@ -438,7 +503,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
438
503
|
end
|
439
504
|
end
|
440
505
|
|
441
|
-
context '#members map', api_private: true do
|
506
|
+
context '#members map / PresenceMap (#RTP2)', api_private: true do
|
442
507
|
it 'is available once the channel is created' do
|
443
508
|
expect(presence_anonymous_client.members).to_not be_nil
|
444
509
|
stop_reactor
|
@@ -490,29 +555,199 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
490
555
|
end
|
491
556
|
end
|
492
557
|
end
|
558
|
+
|
559
|
+
context 'the map is based on the member_key (connection_id & client_id)' do
|
560
|
+
# 2 unqiue client_id with client_id "b" being on two connections
|
561
|
+
let(:enter_action) { 2 }
|
562
|
+
let(:presence_data) do
|
563
|
+
[
|
564
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: enter_action },
|
565
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:1', action: enter_action },
|
566
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:2', action: enter_action },
|
567
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:3', action: enter_action },
|
568
|
+
{ client_id: 'b', connection_id: 'two', id: 'two:0:4', action: enter_action }
|
569
|
+
]
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'ensures uniqueness from this member_key (#RTP2a)' do
|
573
|
+
channel_anonymous_client.attach do
|
574
|
+
presence_anonymous_client.get do |members|
|
575
|
+
expect(members.length).to eql(0)
|
576
|
+
|
577
|
+
## Fabricate members
|
578
|
+
action = Ably::Models::ProtocolMessage::ACTION.Presence
|
579
|
+
presence_msg = Ably::Models::ProtocolMessage.new(
|
580
|
+
action: action,
|
581
|
+
connection_serial: 20,
|
582
|
+
channel: channel_name,
|
583
|
+
presence: presence_data,
|
584
|
+
timestamp: Time.now.to_i * 1000
|
585
|
+
)
|
586
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, presence_msg
|
587
|
+
|
588
|
+
EventMachine.add_timer(0.5) do
|
589
|
+
presence_anonymous_client.get do |members|
|
590
|
+
expect(members.length).to eql(3)
|
591
|
+
expect(members.map { |member| member.client_id }.uniq).to contain_exactly('a', 'b')
|
592
|
+
stop_reactor
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
context 'newness is compared based on PresenceMessage#id unless synthesized' do
|
601
|
+
let(:page_size) { 100 }
|
602
|
+
let(:enter_expected_count) { page_size + 1 } # 100 per page, this ensures we have more than one page so that we can test newness during sync
|
603
|
+
let(:enter_action) { 2 }
|
604
|
+
let(:leave_action) { 3 }
|
605
|
+
let(:now) { Time.now.to_i * 1000 }
|
606
|
+
let(:entered) { [] }
|
607
|
+
let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(auth_callback: wildcard_token)) }
|
608
|
+
|
609
|
+
def setup_members_on(presence)
|
610
|
+
enter_expected_count.times do |indx|
|
611
|
+
# 10 messages per second max rate on simulation accounts
|
612
|
+
rate = indx.to_f / 10
|
613
|
+
EventMachine.add_timer(rate) do
|
614
|
+
presence.enter_client("client:#{indx}") do |message|
|
615
|
+
entered << message
|
616
|
+
next unless entered.count == enter_expected_count
|
617
|
+
yield
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
def allow_sync_fabricate_data_final_sync_and_assert_members
|
624
|
+
setup_members_on(presence_client_one) do
|
625
|
+
sync_pages_received = []
|
626
|
+
anonymous_client.connection.once(:connected) do
|
627
|
+
anonymous_client.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
628
|
+
if protocol_message.action == :sync
|
629
|
+
sync_pages_received << protocol_message
|
630
|
+
if sync_pages_received.count == 1
|
631
|
+
action = Ably::Models::ProtocolMessage::ACTION.Presence
|
632
|
+
presence_msg = Ably::Models::ProtocolMessage.new(
|
633
|
+
action: action,
|
634
|
+
connection_serial: anonymous_client.connection.serial + 1,
|
635
|
+
channel: channel_name,
|
636
|
+
presence: presence_data,
|
637
|
+
timestamp: Time.now.to_i * 1000
|
638
|
+
)
|
639
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, presence_msg
|
640
|
+
|
641
|
+
# Now simulate an end to the sync
|
642
|
+
action = Ably::Models::ProtocolMessage::ACTION.Sync
|
643
|
+
sync_msg = Ably::Models::ProtocolMessage.new(
|
644
|
+
action: action,
|
645
|
+
connection_serial: anonymous_client.connection.serial + 2,
|
646
|
+
channel: channel_name,
|
647
|
+
channel_serial: 'validserialprefix:', # with no part after the `:` this indicates the end to the SYNC
|
648
|
+
presence: [],
|
649
|
+
timestamp: Time.now.to_i * 1000
|
650
|
+
)
|
651
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_msg
|
652
|
+
|
653
|
+
# Stop the next SYNC arriving
|
654
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.unsubscribe
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
presence_anonymous_client.get do |members|
|
660
|
+
expect(members.length).to eql(page_size + 2)
|
661
|
+
expect(members.find { |member| member.client_id == 'a' }).to be_nil
|
662
|
+
expect(members.find { |member| member.client_id == 'b' }.timestamp.to_i).to eql(now / 1000)
|
663
|
+
expect(members.find { |member| member.client_id == 'c' }.timestamp.to_i).to eql(now / 1000)
|
664
|
+
stop_reactor
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
context 'when presence messages are synthesized' do
|
671
|
+
let(:presence_data) do
|
672
|
+
[
|
673
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: enter_action, timestamp: now }, # first messages from client, second fabricated
|
674
|
+
{ client_id: 'a', connection_id: 'one', id: 'fabricated:0:1', action: leave_action, timestamp: now + 1 }, # leave after enter based on timestamp
|
675
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:2', action: enter_action, timestamp: now }, # first messages from client, second fabricated
|
676
|
+
{ client_id: 'b', connection_id: 'one', id: 'fabricated:0:3', action: leave_action, timestamp: now - 1 }, # leave before enter based on timestamp
|
677
|
+
{ client_id: 'c', connection_id: 'one', id: 'fabricated:0:4', action: enter_action, timestamp: now }, # both messages fabricated
|
678
|
+
{ client_id: 'c', connection_id: 'one', id: 'fabricated:0:5', action: leave_action, timestamp: now - 1 } # leave before enter based on timestamp
|
679
|
+
]
|
680
|
+
end
|
681
|
+
|
682
|
+
it 'compares based on timestamp if either has a connectionId not part of the presence message id (#RTP2b1)' do
|
683
|
+
allow_sync_fabricate_data_final_sync_and_assert_members
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
context 'when presence messages are not synthesized (events sent from clients)' do
|
688
|
+
let(:presence_data) do
|
689
|
+
[
|
690
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: enter_action, timestamp: now }, # first messages from client
|
691
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:1:0', action: leave_action, timestamp: now - 1 }, # leave after enter based on msgSerial part of ID
|
692
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:2:2', action: enter_action, timestamp: now }, # first messages from client
|
693
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:2:1', action: leave_action, timestamp: now + 1 }, # leave before enter based on index part of ID
|
694
|
+
{ client_id: 'c', connection_id: 'one', id: 'one:4:4', action: enter_action, timestamp: now }, # first messages from client
|
695
|
+
{ client_id: 'c', connection_id: 'one', id: 'one:3:5', action: leave_action, timestamp: now + 1 } # leave before enter based on msgSerial part of ID
|
696
|
+
]
|
697
|
+
end
|
698
|
+
|
699
|
+
it 'compares based on timestamp if either has a connectionId not part of the presence message id (#RTP2b2)' do
|
700
|
+
allow_sync_fabricate_data_final_sync_and_assert_members
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|
493
704
|
end
|
494
705
|
|
495
|
-
context '#sync_complete?' do
|
706
|
+
context '#sync_complete? and SYNC flags (#RTP1)' do
|
496
707
|
context 'when attaching to a channel without any members present' do
|
497
|
-
it 'is true and the presence channel is considered synced immediately' do
|
708
|
+
it 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do
|
709
|
+
flag_checked = false
|
710
|
+
|
711
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
712
|
+
if protocol_message.action == :attached
|
713
|
+
flag_checked = true
|
714
|
+
expect(protocol_message.has_presence_flag?).to eql(false)
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
498
718
|
channel_anonymous_client.attach do
|
499
719
|
expect(channel_anonymous_client.presence).to be_sync_complete
|
500
|
-
|
720
|
+
EventMachine.next_tick do
|
721
|
+
expect(flag_checked).to eql(true)
|
722
|
+
stop_reactor
|
723
|
+
end
|
501
724
|
end
|
502
725
|
end
|
503
726
|
end
|
504
727
|
|
505
728
|
context 'when attaching to a channel with members present' do
|
506
|
-
it 'is false and the presence channel
|
729
|
+
it 'sync_complete? is false, there is a presence flag, and the presence channel is subsequently synced (#RTP1)' do
|
730
|
+
flag_checked = false
|
731
|
+
|
732
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
733
|
+
if protocol_message.action == :attached
|
734
|
+
flag_checked = true
|
735
|
+
expect(protocol_message.has_presence_flag?).to eql(true)
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
507
739
|
presence_client_one.enter
|
508
740
|
presence_client_one.subscribe(:enter) do
|
509
741
|
presence_client_one.unsubscribe :enter
|
510
742
|
|
511
743
|
channel_anonymous_client.attach do
|
512
744
|
expect(channel_anonymous_client.presence).to_not be_sync_complete
|
513
|
-
channel_anonymous_client.presence.get
|
745
|
+
channel_anonymous_client.presence.get do
|
514
746
|
expect(channel_anonymous_client.presence).to be_sync_complete
|
515
|
-
|
747
|
+
EventMachine.next_tick do
|
748
|
+
expect(flag_checked).to eql(true)
|
749
|
+
stop_reactor
|
750
|
+
end
|
516
751
|
end
|
517
752
|
end
|
518
753
|
end
|
@@ -520,19 +755,20 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
520
755
|
end
|
521
756
|
end
|
522
757
|
|
523
|
-
context '
|
524
|
-
context '
|
525
|
-
let(:enter_expected_count) {
|
758
|
+
context '101 existing (present) members on a channel (2 SYNC pages)' do
|
759
|
+
context 'requiring at least 2 SYNC ProtocolMessages', em_timeout: 40 do
|
760
|
+
let(:enter_expected_count) { 101 }
|
526
761
|
let(:present) { [] }
|
527
762
|
let(:entered) { [] }
|
528
763
|
let(:sync_pages_received) { [] }
|
529
764
|
let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
|
530
765
|
|
531
766
|
def setup_members_on(presence)
|
532
|
-
enter_expected_count.times do |
|
767
|
+
enter_expected_count.times do |indx|
|
533
768
|
# 10 messages per second max rate on simulation accounts
|
534
|
-
|
535
|
-
|
769
|
+
rate = indx.to_f / 10
|
770
|
+
EventMachine.add_timer(rate) do
|
771
|
+
presence.enter_client("client:#{indx}") do |message|
|
536
772
|
entered << message
|
537
773
|
next unless entered.count == enter_expected_count
|
538
774
|
yield
|
@@ -555,8 +791,47 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
555
791
|
end
|
556
792
|
end
|
557
793
|
|
794
|
+
context 'and a member enters before the SYNC operation is complete' do
|
795
|
+
let(:enter_client_id) { random_str }
|
796
|
+
|
797
|
+
it 'emits a :enter immediately and the member is :present once the sync is complete (#RTP2g)' do
|
798
|
+
setup_members_on(presence_client_one) do
|
799
|
+
member_entered = false
|
800
|
+
|
801
|
+
anonymous_client.connect do
|
802
|
+
presence_anonymous_client.subscribe(:enter) do |member|
|
803
|
+
expect(member.client_id).to eql(enter_client_id)
|
804
|
+
member_entered = true
|
805
|
+
end
|
806
|
+
|
807
|
+
presence_anonymous_client.get do |members|
|
808
|
+
expect(members.find { |member| member.client_id == enter_client_id }.action).to eq(:present)
|
809
|
+
stop_reactor
|
810
|
+
end
|
811
|
+
|
812
|
+
anonymous_client.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
813
|
+
if protocol_message.action == :sync
|
814
|
+
sync_pages_received << protocol_message
|
815
|
+
if sync_pages_received.count == 1
|
816
|
+
enter_action = Ably::Models::PresenceMessage::ACTION.Enter
|
817
|
+
enter_member = Ably::Models::PresenceMessage.new(
|
818
|
+
'id' => "#{client_one.connection.id}:#{random_str}:0",
|
819
|
+
'clientId' => enter_client_id,
|
820
|
+
'connectionId' => client_one.connection.id,
|
821
|
+
'timestamp' => as_since_epoch(Time.now),
|
822
|
+
'action' => enter_action
|
823
|
+
)
|
824
|
+
presence_anonymous_client.__incoming_msgbus__.publish :presence, enter_member
|
825
|
+
end
|
826
|
+
end
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
558
833
|
context 'and a member leaves before the SYNC operation is complete' do
|
559
|
-
it 'emits :leave immediately as the member leaves' do
|
834
|
+
it 'emits :leave immediately as the member leaves and cleans up the ABSENT member after (#RTP2f, #RTP2g)' do
|
560
835
|
all_client_ids = enter_expected_count.times.map { |id| "client:#{id}" }
|
561
836
|
|
562
837
|
setup_members_on(presence_client_one) do
|
@@ -570,7 +845,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
570
845
|
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
571
846
|
expect(leave_message.client_id).to eql(leave_member.client_id)
|
572
847
|
expect(present.count).to be < enter_expected_count
|
573
|
-
|
848
|
+
|
849
|
+
# Hacky accessing a private method, but absent members are intentionally not exposed to any public APIs
|
850
|
+
expect(presence_anonymous_client.members.send(:absent_members).length).to eql(1)
|
851
|
+
|
852
|
+
presence_anonymous_client.members.once(:in_sync) do
|
853
|
+
# Check that members count is exact indicating the members with LEAVE action after sync are removed
|
854
|
+
expect(presence_anonymous_client).to be_sync_complete
|
855
|
+
expect(presence_anonymous_client.members.length).to eql(enter_expected_count - 1)
|
574
856
|
presence_anonymous_client.unsubscribe
|
575
857
|
stop_reactor
|
576
858
|
end
|
@@ -583,7 +865,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
583
865
|
if sync_pages_received.count == 1
|
584
866
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
585
867
|
leave_member = Ably::Models::PresenceMessage.new(
|
586
|
-
'id' => "#{client_one.connection.id}
|
868
|
+
'id' => "#{client_one.connection.id}:#{all_client_ids.first}:0",
|
587
869
|
'clientId' => all_client_ids.first,
|
588
870
|
'connectionId' => client_one.connection.id,
|
589
871
|
'timestamp' => as_since_epoch(Time.now),
|
@@ -597,7 +879,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
597
879
|
end
|
598
880
|
end
|
599
881
|
|
600
|
-
it 'ignores presence events with timestamps prior to the current :present event in the MembersMap' do
|
882
|
+
it 'ignores presence events with timestamps / identifiers prior to the current :present event in the MembersMap (#RTP2c)' do
|
601
883
|
started_at = Time.now
|
602
884
|
|
603
885
|
setup_members_on(presence_client_one) do
|
@@ -605,11 +887,12 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
605
887
|
|
606
888
|
presence_anonymous_client.subscribe(:present) do |present_message|
|
607
889
|
present << present_message
|
608
|
-
leave_member = present_message unless leave_member
|
609
890
|
|
610
891
|
if present.count == enter_expected_count
|
611
892
|
presence_anonymous_client.get do |members|
|
612
|
-
|
893
|
+
member = members.find { |member| member.client_id == leave_member.client_id}
|
894
|
+
expect(member).to_not be_nil
|
895
|
+
expect(member.action).to eq(:present)
|
613
896
|
EventMachine.add_timer(1) do
|
614
897
|
presence_anonymous_client.unsubscribe
|
615
898
|
stop_reactor
|
@@ -619,7 +902,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
619
902
|
end
|
620
903
|
|
621
904
|
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
622
|
-
raise
|
905
|
+
raise "Leave event for #{leave_message} should not have been fired because it is out of date"
|
623
906
|
end
|
624
907
|
|
625
908
|
anonymous_client.connect do
|
@@ -627,10 +910,12 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
627
910
|
if protocol_message.action == :sync
|
628
911
|
sync_pages_received << protocol_message
|
629
912
|
if sync_pages_received.count == 1
|
913
|
+
first_member = protocol_message.presence[0] # get the first member in the SYNC set
|
630
914
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
631
915
|
leave_member = Ably::Models::PresenceMessage.new(
|
632
|
-
|
916
|
+
first_member.as_json.merge('action' => leave_action, 'timestamp' => as_since_epoch(started_at))
|
633
917
|
)
|
918
|
+
# After the SYNC has started, no inject that member has having left with a timestamp before the sync
|
634
919
|
presence_anonymous_client.__incoming_msgbus__.publish :presence, leave_member
|
635
920
|
end
|
636
921
|
end
|
@@ -639,7 +924,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
639
924
|
end
|
640
925
|
end
|
641
926
|
|
642
|
-
it 'does not emit :present after the :leave event has been emitted, and that member is not included in the list of members via #get
|
927
|
+
it 'does not emit :present after the :leave event has been emitted, and that member is not included in the list of members via #get (#RTP2f)' do
|
643
928
|
left_client = 10
|
644
929
|
left_client_id = "client:#{left_client}"
|
645
930
|
|
@@ -661,7 +946,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
661
946
|
member_left_emitted = true
|
662
947
|
end
|
663
948
|
|
664
|
-
presence_anonymous_client.get
|
949
|
+
presence_anonymous_client.get do |members|
|
665
950
|
expect(members.count).to eql(enter_expected_count - 1)
|
666
951
|
expect(member_left_emitted).to eql(true)
|
667
952
|
expect(members.map(&:client_id)).to_not include(left_client_id)
|
@@ -674,7 +959,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
674
959
|
channel_anonymous_client.attach do
|
675
960
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
676
961
|
fake_leave_presence_message = Ably::Models::PresenceMessage.new(
|
677
|
-
'id' => "#{client_one.connection.id}
|
962
|
+
'id' => "#{client_one.connection.id}:#{left_client_id}:0",
|
678
963
|
'clientId' => left_client_id,
|
679
964
|
'connectionId' => client_one.connection.id,
|
680
965
|
'timestamp' => as_since_epoch(Time.now),
|
@@ -688,11 +973,11 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
688
973
|
end
|
689
974
|
|
690
975
|
context '#get' do
|
691
|
-
context '
|
692
|
-
it 'waits until sync is complete', em_timeout: 30 do # allow for slow connections and lots of messages
|
693
|
-
enter_expected_count.times do |
|
694
|
-
EventMachine.add_timer(
|
695
|
-
presence_client_one.enter_client
|
976
|
+
context 'by default' do
|
977
|
+
it 'waits until sync is complete (#RTP11c1)', em_timeout: 30 do # allow for slow connections and lots of messages
|
978
|
+
enter_expected_count.times do |indx|
|
979
|
+
EventMachine.add_timer(indx / 10) do
|
980
|
+
presence_client_one.enter_client "client:#{indx}"
|
696
981
|
end
|
697
982
|
end
|
698
983
|
|
@@ -700,7 +985,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
700
985
|
entered << message
|
701
986
|
next unless entered.count == enter_expected_count
|
702
987
|
|
703
|
-
presence_anonymous_client.get
|
988
|
+
presence_anonymous_client.get do |members|
|
704
989
|
expect(members.map(&:client_id).uniq.count).to eql(enter_expected_count)
|
705
990
|
expect(members.count).to eql(enter_expected_count)
|
706
991
|
stop_reactor
|
@@ -709,23 +994,22 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
709
994
|
end
|
710
995
|
end
|
711
996
|
|
712
|
-
context '
|
997
|
+
context 'with :wait_for_sync option set to false (#RTP11c1)' do
|
713
998
|
it 'it does not wait for sync', em_timeout: 30 do # allow for slow connections and lots of messages
|
714
999
|
enter_expected_count.times do |indx|
|
715
1000
|
EventMachine.add_timer(indx / 10) do
|
716
1001
|
presence_client_one.enter_client "client:#{indx}"
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
stop_reactor
|
1002
|
+
presence_client_one.subscribe(:enter) do |message|
|
1003
|
+
entered << message
|
1004
|
+
next unless entered.count == enter_expected_count
|
1005
|
+
|
1006
|
+
channel_anonymous_client.attach do
|
1007
|
+
presence_anonymous_client.get(wait_for_sync: false) do |members|
|
1008
|
+
expect(presence_anonymous_client.members).to_not be_in_sync
|
1009
|
+
expect(members.count).to eql(0)
|
1010
|
+
stop_reactor
|
1011
|
+
end
|
1012
|
+
end
|
729
1013
|
end
|
730
1014
|
end
|
731
1015
|
end
|
@@ -937,9 +1221,16 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
937
1221
|
end
|
938
1222
|
end
|
939
1223
|
|
940
|
-
it '
|
941
|
-
|
942
|
-
|
1224
|
+
it 'succeeds and does not emit an event (#RTP10d)' do
|
1225
|
+
channel_client_one.presence.leave do
|
1226
|
+
# allow enough time for leave event to (not) fire
|
1227
|
+
EventMachine.add_timer(2) do
|
1228
|
+
stop_reactor
|
1229
|
+
end
|
1230
|
+
end
|
1231
|
+
channel_client_one.subscribe(:leave) do
|
1232
|
+
raise "No leave event should fire"
|
1233
|
+
end
|
943
1234
|
end
|
944
1235
|
|
945
1236
|
it_should_behave_like 'a public presence method', :leave, :left, {}, enter_first: true
|
@@ -1219,28 +1510,75 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1219
1510
|
end
|
1220
1511
|
|
1221
1512
|
it 'catches exceptions in the provided method block' do
|
1222
|
-
expect(presence_client_one.logger).to receive(:error)
|
1513
|
+
expect(presence_client_one.logger).to receive(:error) do |*args, &block|
|
1514
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
|
1223
1515
|
stop_reactor
|
1224
1516
|
end
|
1225
1517
|
presence_client_one.get { raise 'Intentional exception' }
|
1226
1518
|
end
|
1227
1519
|
|
1228
|
-
it '
|
1520
|
+
it 'implicitly attaches the channel (#RTP11b)' do
|
1521
|
+
expect(channel_client_one).to be_initialized
|
1522
|
+
presence_client_one.get do |members|
|
1523
|
+
expect(channel_client_one).to be_attached
|
1524
|
+
stop_reactor
|
1525
|
+
end
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
context 'when the channel is SUSPENDED' do
|
1529
|
+
context 'with wait_for_sync: true' do
|
1530
|
+
it 'results in an error with @code@ @91005@ and a @message@ stating that the presence state is out of sync (#RTP11d)' do
|
1531
|
+
presence_client_one.enter do
|
1532
|
+
channel_client_one.transition_state_machine! :suspended
|
1533
|
+
presence_client_one.get(wait_for_sync: true).errback do |error|
|
1534
|
+
expect(error.code).to eql(91005)
|
1535
|
+
expect(error.message).to match(/presence state is out of sync/i)
|
1536
|
+
stop_reactor
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
end
|
1540
|
+
end
|
1541
|
+
|
1542
|
+
context 'with wait_for_sync: false' do
|
1543
|
+
it 'returns the current PresenceMap and does not wait for the channel to change to the ATTACHED state (#RTP11d)' do
|
1544
|
+
presence_client_one.enter do
|
1545
|
+
channel_client_one.transition_state_machine! :suspended
|
1546
|
+
presence_client_one.get(wait_for_sync: false) do |members|
|
1547
|
+
expect(channel_client_one).to be_suspended
|
1548
|
+
stop_reactor
|
1549
|
+
end
|
1550
|
+
end
|
1551
|
+
end
|
1552
|
+
end
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
it 'fails if the connection is DETACHED (#RTP11b)' do
|
1229
1556
|
channel_client_one.attach do
|
1230
|
-
channel_client_one.
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1557
|
+
channel_client_one.detach do
|
1558
|
+
presence_client_one.get.tap do |deferrable|
|
1559
|
+
deferrable.callback { raise 'Get should not succeed' }
|
1560
|
+
deferrable.errback do |error|
|
1561
|
+
expect(error).to be_a(Ably::Exceptions::InvalidState)
|
1562
|
+
expect(error.message).to match(/Operation is not allowed when channel is in STATE.Detached/)
|
1563
|
+
stop_reactor
|
1564
|
+
end
|
1565
|
+
end
|
1234
1566
|
end
|
1235
1567
|
end
|
1236
1568
|
end
|
1237
1569
|
|
1238
|
-
it '
|
1570
|
+
it 'fails if the connection is FAILED (#RTP11b)' do
|
1239
1571
|
channel_client_one.attach do
|
1240
1572
|
channel_client_one.transition_state_machine :failed
|
1241
1573
|
expect(channel_client_one.state).to eq(:failed)
|
1242
|
-
|
1243
|
-
|
1574
|
+
presence_client_one.get.tap do |deferrable|
|
1575
|
+
deferrable.callback { raise 'Get should not succeed' }
|
1576
|
+
deferrable.errback do |error|
|
1577
|
+
expect(error).to be_a(Ably::Exceptions::InvalidState)
|
1578
|
+
expect(error.message).to match(/Operation is not allowed when channel is in STATE.Failed/)
|
1579
|
+
stop_reactor
|
1580
|
+
end
|
1581
|
+
end
|
1244
1582
|
end
|
1245
1583
|
end
|
1246
1584
|
|
@@ -1252,11 +1590,11 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1252
1590
|
let(:client_options) { default_options.merge(log_level: :none) }
|
1253
1591
|
|
1254
1592
|
def connect_members_deferrables
|
1255
|
-
(members_per_page * pages + 1).times.map do |
|
1593
|
+
(members_per_page * pages + 1).times.map do |mem_index|
|
1256
1594
|
# rate limit to 10 per second
|
1257
1595
|
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
1258
|
-
EventMachine.add_timer(
|
1259
|
-
presence_client_one.enter_client("client:#{
|
1596
|
+
EventMachine.add_timer(mem_index/10) do
|
1597
|
+
presence_client_one.enter_client("client:#{mem_index}").tap do |enter_deferrable|
|
1260
1598
|
enter_deferrable.callback { |*args| deferrable.succeed *args }
|
1261
1599
|
enter_deferrable.errback { |*args| deferrable.fail *args }
|
1262
1600
|
end
|
@@ -1266,7 +1604,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1266
1604
|
end
|
1267
1605
|
|
1268
1606
|
context 'when :wait_for_sync is true' do
|
1269
|
-
it 'fails if the connection
|
1607
|
+
it 'fails if the connection becomes FAILED (#RTP11b)' do
|
1270
1608
|
when_all(*connect_members_deferrables) do
|
1271
1609
|
channel_client_two.attach do
|
1272
1610
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
@@ -1289,7 +1627,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1289
1627
|
end
|
1290
1628
|
end
|
1291
1629
|
|
1292
|
-
it 'fails if the channel
|
1630
|
+
it 'fails if the channel becomes detached (#RTP11b)' do
|
1293
1631
|
when_all(*connect_members_deferrables) do
|
1294
1632
|
channel_client_two.attach do
|
1295
1633
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
@@ -1313,9 +1651,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1313
1651
|
end
|
1314
1652
|
end
|
1315
1653
|
|
1316
|
-
|
1317
|
-
|
1318
|
-
it 'returns the current members on the channel' do
|
1654
|
+
it 'returns the current members on the channel (#RTP11a)' do
|
1319
1655
|
presence_client_one.enter
|
1320
1656
|
presence_client_one.subscribe(:enter) do
|
1321
1657
|
presence_client_one.unsubscribe :enter
|
@@ -1332,7 +1668,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1332
1668
|
end
|
1333
1669
|
end
|
1334
1670
|
|
1335
|
-
it 'filters by connection_id option if provided' do
|
1671
|
+
it 'filters by connection_id option if provided (#RTP11c3)' do
|
1336
1672
|
presence_client_one.enter do
|
1337
1673
|
presence_client_two.enter
|
1338
1674
|
end
|
@@ -1354,7 +1690,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1354
1690
|
end
|
1355
1691
|
end
|
1356
1692
|
|
1357
|
-
it 'filters by client_id option if provided' do
|
1693
|
+
it 'filters by client_id option if provided (#RTP11c2)' do
|
1358
1694
|
presence_client_one.enter do
|
1359
1695
|
presence_client_two.enter
|
1360
1696
|
end
|
@@ -1378,7 +1714,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1378
1714
|
end
|
1379
1715
|
end
|
1380
1716
|
|
1381
|
-
it 'does not wait for SYNC to complete if :wait_for_sync option is false' do
|
1717
|
+
it 'does not wait for SYNC to complete if :wait_for_sync option is false (#RTP11c1)' do
|
1382
1718
|
presence_client_one.enter
|
1383
1719
|
presence_client_one.subscribe(:enter) do
|
1384
1720
|
presence_client_one.unsubscribe :enter
|
@@ -1390,6 +1726,18 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1390
1726
|
end
|
1391
1727
|
end
|
1392
1728
|
|
1729
|
+
it 'returns the list of members and waits for SYNC to complete by default (#RTP11a)' do
|
1730
|
+
presence_client_one.enter
|
1731
|
+
presence_client_one.subscribe(:enter) do
|
1732
|
+
presence_client_one.unsubscribe :enter
|
1733
|
+
|
1734
|
+
presence_client_two.get do |members|
|
1735
|
+
expect(members.count).to eql(1)
|
1736
|
+
stop_reactor
|
1737
|
+
end
|
1738
|
+
end
|
1739
|
+
end
|
1740
|
+
|
1393
1741
|
context 'when a member enters and then leaves' do
|
1394
1742
|
it 'has no members' do
|
1395
1743
|
presence_client_one.enter do
|
@@ -1405,6 +1753,21 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1405
1753
|
end
|
1406
1754
|
end
|
1407
1755
|
|
1756
|
+
context 'when a member enters and the presence map is updated' do
|
1757
|
+
it 'adds the member as being :present (#RTP2d)' do
|
1758
|
+
presence_client_one.enter
|
1759
|
+
presence_client_one.subscribe(:enter) do
|
1760
|
+
presence_client_one.unsubscribe :enter
|
1761
|
+
|
1762
|
+
presence_client_one.get do |members|
|
1763
|
+
expect(members.count).to eq(1)
|
1764
|
+
expect(members.first.action).to eq(:present)
|
1765
|
+
stop_reactor
|
1766
|
+
end
|
1767
|
+
end
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1408
1771
|
context 'with lots of members on different clients' do
|
1409
1772
|
let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
|
1410
1773
|
let(:client_two) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
|
@@ -1413,9 +1776,9 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1413
1776
|
let(:total_members) { members_per_client * 2 }
|
1414
1777
|
|
1415
1778
|
it 'returns a complete list of members on all clients' do
|
1416
|
-
members_per_client.times do |
|
1417
|
-
presence_client_one.enter_client("client_1:#{
|
1418
|
-
presence_client_two.enter_client("client_2:#{
|
1779
|
+
members_per_client.times do |indx|
|
1780
|
+
presence_client_one.enter_client("client_1:#{indx}")
|
1781
|
+
presence_client_two.enter_client("client_2:#{indx}")
|
1419
1782
|
end
|
1420
1783
|
|
1421
1784
|
presence_client_one.subscribe(:enter) do
|
@@ -1502,7 +1865,9 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1502
1865
|
|
1503
1866
|
it 'logs the error and continues' do
|
1504
1867
|
emitted_exception = false
|
1505
|
-
expect(client_one.logger).to receive(:error)
|
1868
|
+
expect(client_one.logger).to receive(:error) do |*args, &block|
|
1869
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/#{exception.message}/)
|
1870
|
+
end
|
1506
1871
|
presence_client_one.subscribe do |presence_message|
|
1507
1872
|
emitted_exception = true
|
1508
1873
|
raise exception
|
@@ -1737,9 +2102,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1737
2102
|
|
1738
2103
|
it 'emits an error when cipher does not match and presence data cannot be decoded' do
|
1739
2104
|
incompatible_encrypted_channel.attach do
|
1740
|
-
|
1741
|
-
expect(
|
1742
|
-
expect(error.message).to match(/Cipher algorithm AES-128-CBC does not match/)
|
2105
|
+
expect(client_two.logger).to receive(:error) do |*args, &block|
|
2106
|
+
expect(args.concat([block ? block.call : nil]).join(',')).to match(/Cipher algorithm AES-128-CBC does not match/)
|
1743
2107
|
stop_reactor
|
1744
2108
|
end
|
1745
2109
|
|
@@ -1777,13 +2141,13 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1777
2141
|
end
|
1778
2142
|
|
1779
2143
|
context 'connection failure mid-way through a large member sync' do
|
1780
|
-
let(:members_count) {
|
2144
|
+
let(:members_count) { 201 }
|
1781
2145
|
let(:sync_pages_received) { [] }
|
1782
2146
|
let(:client_options) { default_options.merge(log_level: :fatal) }
|
1783
2147
|
|
1784
|
-
it 'resumes the SYNC operation', em_timeout: 15 do
|
1785
|
-
when_all(*members_count.times.map do |
|
1786
|
-
presence_anonymous_client.enter_client("client:#{
|
2148
|
+
it 'resumes the SYNC operation (#RTP3)', em_timeout: 15 do
|
2149
|
+
when_all(*members_count.times.map do |indx|
|
2150
|
+
presence_anonymous_client.enter_client("client:#{indx}")
|
1787
2151
|
end) do
|
1788
2152
|
channel_client_two.attach do
|
1789
2153
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
@@ -1802,5 +2166,655 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1802
2166
|
end
|
1803
2167
|
end
|
1804
2168
|
end
|
2169
|
+
|
2170
|
+
context 'server-initiated sync' do
|
2171
|
+
context 'with multiple SYNC pages' do
|
2172
|
+
let(:present_action) { 1 }
|
2173
|
+
let(:leave_action) { 3 }
|
2174
|
+
let(:presence_sync_1) do
|
2175
|
+
[
|
2176
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: present_action },
|
2177
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:1', action: present_action }
|
2178
|
+
]
|
2179
|
+
end
|
2180
|
+
let(:presence_sync_2) do
|
2181
|
+
[
|
2182
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:1:0', action: leave_action }
|
2183
|
+
]
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
it 'is initiated with a SYNC message and completed with a later SYNC message with no cursor value part of the channelSerial (#RTP18a, #RTP18b) ', em_timeout: 15 do
|
2187
|
+
presence_anonymous_client.get do |members|
|
2188
|
+
expect(members.length).to eql(0)
|
2189
|
+
expect(presence_anonymous_client).to be_sync_complete
|
2190
|
+
|
2191
|
+
presence_anonymous_client.subscribe(:present) do
|
2192
|
+
expect(presence_anonymous_client).to_not be_sync_complete
|
2193
|
+
presence_anonymous_client.get do |members|
|
2194
|
+
expect(presence_anonymous_client).to be_sync_complete
|
2195
|
+
expect(members.length).to eql(1)
|
2196
|
+
expect(members.first.client_id).to eql('b')
|
2197
|
+
stop_reactor
|
2198
|
+
end
|
2199
|
+
end
|
2200
|
+
|
2201
|
+
## Fabricate server-initiated SYNC in two parts
|
2202
|
+
action = Ably::Models::ProtocolMessage::ACTION.Sync
|
2203
|
+
sync_message = Ably::Models::ProtocolMessage.new(
|
2204
|
+
action: action,
|
2205
|
+
connection_serial: 10,
|
2206
|
+
channel_serial: 'sequenceid:cursor',
|
2207
|
+
channel: channel_name,
|
2208
|
+
presence: presence_sync_1,
|
2209
|
+
timestamp: Time.now.to_i * 1000
|
2210
|
+
)
|
2211
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message
|
2212
|
+
|
2213
|
+
sync_message = Ably::Models::ProtocolMessage.new(
|
2214
|
+
action: action,
|
2215
|
+
connection_serial: 11,
|
2216
|
+
channel_serial: 'sequenceid:', # indicates SYNC is complete
|
2217
|
+
channel: channel_name,
|
2218
|
+
presence: presence_sync_2,
|
2219
|
+
timestamp: Time.now.to_i * 1000
|
2220
|
+
)
|
2221
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message
|
2222
|
+
end
|
2223
|
+
end
|
2224
|
+
end
|
2225
|
+
|
2226
|
+
context 'with a single SYNC page' do
|
2227
|
+
let(:present_action) { 1 }
|
2228
|
+
let(:leave_action) { 3 }
|
2229
|
+
let(:presence_sync) do
|
2230
|
+
[
|
2231
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: present_action },
|
2232
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:1', action: present_action },
|
2233
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:1:0', action: leave_action }
|
2234
|
+
]
|
2235
|
+
end
|
2236
|
+
|
2237
|
+
it 'is initiated and completed with a single SYNC message (and no channelSerial) (#RTP18a, #RTP18c) ', em_timeout: 15 do
|
2238
|
+
presence_anonymous_client.get do |members|
|
2239
|
+
expect(members.length).to eql(0)
|
2240
|
+
expect(presence_anonymous_client).to be_sync_complete
|
2241
|
+
|
2242
|
+
presence_anonymous_client.subscribe(:present) do
|
2243
|
+
expect(presence_anonymous_client).to_not be_sync_complete
|
2244
|
+
presence_anonymous_client.get do |members|
|
2245
|
+
expect(presence_anonymous_client).to be_sync_complete
|
2246
|
+
expect(members.length).to eql(1)
|
2247
|
+
expect(members.first.client_id).to eql('b')
|
2248
|
+
stop_reactor
|
2249
|
+
end
|
2250
|
+
end
|
2251
|
+
|
2252
|
+
## Fabricate server-initiated SYNC in two parts
|
2253
|
+
action = Ably::Models::ProtocolMessage::ACTION.Sync
|
2254
|
+
sync_message = Ably::Models::ProtocolMessage.new(
|
2255
|
+
action: action,
|
2256
|
+
connection_serial: 10,
|
2257
|
+
channel: channel_name,
|
2258
|
+
presence: presence_sync,
|
2259
|
+
timestamp: Time.now.to_i * 1000
|
2260
|
+
)
|
2261
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message
|
2262
|
+
end
|
2263
|
+
end
|
2264
|
+
end
|
2265
|
+
|
2266
|
+
context 'when members exist in the PresenceMap before a SYNC completes' do
|
2267
|
+
let(:enter_action) { Ably::Models::PresenceMessage::ACTION.Enter.to_i }
|
2268
|
+
let(:present_action) { Ably::Models::PresenceMessage::ACTION.Present.to_i }
|
2269
|
+
let(:presence_sync_protocol_message) do
|
2270
|
+
[
|
2271
|
+
{ client_id: 'a', connection_id: 'one', id: 'one:0:0', action: present_action },
|
2272
|
+
{ client_id: 'b', connection_id: 'one', id: 'one:0:1', action: present_action }
|
2273
|
+
]
|
2274
|
+
end
|
2275
|
+
let(:presence_enter_message) do
|
2276
|
+
Ably::Models::PresenceMessage.new(
|
2277
|
+
'id' => "#{random_str}:#{random_str}:0",
|
2278
|
+
'clientId' => random_str,
|
2279
|
+
'connectionId' => random_str,
|
2280
|
+
'timestamp' => as_since_epoch(Time.now),
|
2281
|
+
'action' => enter_action
|
2282
|
+
)
|
2283
|
+
end
|
2284
|
+
|
2285
|
+
it 'removes the members that are no longer present (#RTP19)', em_timeout: 15 do
|
2286
|
+
presence_anonymous_client.get do |members|
|
2287
|
+
expect(members.length).to eql(0)
|
2288
|
+
|
2289
|
+
# Now inject a fake member into the PresenceMap by faking the receive of a Presence message from Ably into the Presence object
|
2290
|
+
presence_anonymous_client.__incoming_msgbus__.publish :presence, presence_enter_message
|
2291
|
+
|
2292
|
+
EventMachine.next_tick do
|
2293
|
+
presence_anonymous_client.get do |members|
|
2294
|
+
expect(members.length).to eql(1)
|
2295
|
+
expect(members.first.client_id).to eql(presence_enter_message.client_id)
|
2296
|
+
|
2297
|
+
presence_events = []
|
2298
|
+
presence_anonymous_client.subscribe do |presence_message|
|
2299
|
+
presence_events << [presence_message.client_id, presence_message.action.to_sym]
|
2300
|
+
if presence_message.action == :leave
|
2301
|
+
expect(presence_message.id).to be_nil
|
2302
|
+
expect(presence_message.timestamp.to_f * 1000).to be_within(20).of(Time.now.to_f * 1000)
|
2303
|
+
end
|
2304
|
+
end
|
2305
|
+
|
2306
|
+
## Fabricate server-initiated SYNC in two parts
|
2307
|
+
action = Ably::Models::ProtocolMessage::ACTION.Sync
|
2308
|
+
sync_message = Ably::Models::ProtocolMessage.new(
|
2309
|
+
action: action,
|
2310
|
+
connection_serial: 10,
|
2311
|
+
channel: channel_name,
|
2312
|
+
presence: presence_sync_protocol_message,
|
2313
|
+
timestamp: Time.now.to_i * 1000
|
2314
|
+
)
|
2315
|
+
anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message
|
2316
|
+
|
2317
|
+
EventMachine.next_tick do
|
2318
|
+
presence_anonymous_client.get do |members|
|
2319
|
+
expect(members.length).to eql(2)
|
2320
|
+
expect(members.find { |member| member.client_id == presence_enter_message.client_id}).to be_nil
|
2321
|
+
expect(presence_events).to contain_exactly(
|
2322
|
+
['a', :present],
|
2323
|
+
['b', :present],
|
2324
|
+
[presence_enter_message.client_id, :leave]
|
2325
|
+
)
|
2326
|
+
stop_reactor
|
2327
|
+
end
|
2328
|
+
end
|
2329
|
+
end
|
2330
|
+
end
|
2331
|
+
end
|
2332
|
+
end
|
2333
|
+
end
|
2334
|
+
end
|
2335
|
+
|
2336
|
+
context 'when the client does not have presence subscribe privileges but is present on the channel' do
|
2337
|
+
let(:present_only_capability) do
|
2338
|
+
{ channel_name => ["presence"] }
|
2339
|
+
end
|
2340
|
+
let(:present_only_callback) { Proc.new { Ably::Rest::Client.new(client_options).auth.request_token(client_id: '*', capability: present_only_capability) } }
|
2341
|
+
let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: present_only_callback)) }
|
2342
|
+
|
2343
|
+
it 'receives presence updates for all presence events generated by the current connection and the presence map is kept up to date (#RTP17a)' do
|
2344
|
+
skip 'This functionality is not yet in sandbox, see https://github.com/ably/realtime/issues/656'
|
2345
|
+
|
2346
|
+
enter_client_ids = []
|
2347
|
+
presence_client_one.subscribe(:enter) do |presence_message|
|
2348
|
+
enter_client_ids << presence_message.client_id
|
2349
|
+
end
|
2350
|
+
|
2351
|
+
leave_client_ids = []
|
2352
|
+
presence_client_one.subscribe(:leave) do
|
2353
|
+
leave_client_ids << presence_message.client_id
|
2354
|
+
end
|
2355
|
+
|
2356
|
+
presence_client_one.enter_client 'bob' do
|
2357
|
+
presence_client_one.enter_client 'sarah'
|
2358
|
+
end
|
2359
|
+
|
2360
|
+
entered_count = 0
|
2361
|
+
presence_client_one.subscribe(:enter) do
|
2362
|
+
entered_count += 1
|
2363
|
+
next unless entered_count == 2
|
2364
|
+
|
2365
|
+
presence_client_one.unsubscribe :enter
|
2366
|
+
presence_client_one.get do |members|
|
2367
|
+
EventMachine.add_timer(1) do
|
2368
|
+
expect(members.map(&:client_id)).to contain_exactly('bob', 'sarah')
|
2369
|
+
expect(enter_client_ids).to contain_exactly('bob', 'sarah')
|
2370
|
+
|
2371
|
+
presence_client_one.leave_client 'bob' do
|
2372
|
+
presence_client_one.leave_client 'sarah'
|
2373
|
+
end
|
2374
|
+
|
2375
|
+
leave_count = 0
|
2376
|
+
presence_client_one.subscribe(:leave) do
|
2377
|
+
leave_count += 1
|
2378
|
+
next unless leave_count == 2
|
2379
|
+
|
2380
|
+
presence_client_one.get do |members|
|
2381
|
+
expect(members.length).to eql(0)
|
2382
|
+
expect(leave_client_ids).to contain_exactly('bob', 'sarah')
|
2383
|
+
stop_reactor
|
2384
|
+
end
|
2385
|
+
end
|
2386
|
+
end
|
2387
|
+
end
|
2388
|
+
end
|
2389
|
+
end
|
2390
|
+
end
|
2391
|
+
|
2392
|
+
context "local PresenceMap for presence members entered by this client" do
|
2393
|
+
it "maintains a copy of the member map for any member that shares this connection's connection ID (#RTP17)" do
|
2394
|
+
presence_client_one.enter do
|
2395
|
+
presence_client_two.enter
|
2396
|
+
end
|
2397
|
+
|
2398
|
+
entered_count = 0
|
2399
|
+
presence_client_one.subscribe(:enter) do
|
2400
|
+
entered_count += 1
|
2401
|
+
next unless entered_count == 2
|
2402
|
+
channel_anonymous_client.attach do
|
2403
|
+
channel_anonymous_client.presence.get do |members|
|
2404
|
+
expect(channel_anonymous_client.presence.members.local_members).to be_empty
|
2405
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2406
|
+
expect(presence_client_one.members.local_members.values.first.connection_id).to eql(client_one.connection.id)
|
2407
|
+
expect(presence_client_two.members.local_members.values.first.connection_id).to eql(client_two.connection.id)
|
2408
|
+
presence_client_two.leave
|
2409
|
+
presence_client_two.subscribe(:leave) do
|
2410
|
+
expect(presence_client_two.members.local_members).to be_empty
|
2411
|
+
stop_reactor
|
2412
|
+
end
|
2413
|
+
end
|
2414
|
+
end
|
2415
|
+
end
|
2416
|
+
end
|
2417
|
+
|
2418
|
+
context 'when a channel becomes attached again' do
|
2419
|
+
let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached.to_i }
|
2420
|
+
let(:sync_action) { Ably::Models::ProtocolMessage::ACTION.Sync.to_i }
|
2421
|
+
let(:presence_action) { Ably::Models::ProtocolMessage::ACTION.Presence.to_i }
|
2422
|
+
let(:present_action) { Ably::Models::PresenceMessage::ACTION.Present.to_i }
|
2423
|
+
let(:resume_flag) { 4 }
|
2424
|
+
let(:presence_flag) { 1 }
|
2425
|
+
|
2426
|
+
def fabricate_incoming_protocol_message(protocol_message)
|
2427
|
+
client_one.connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
2428
|
+
end
|
2429
|
+
|
2430
|
+
# Prevents any messages from the WebSocket transport being sent / received
|
2431
|
+
# Connection protocol message subscriptions are still active, but nothing reaches or comes from the WebSocket transport
|
2432
|
+
def cripple_websocket_transport
|
2433
|
+
client_one.connection.transport.__incoming_protocol_msgbus__.unsubscribe
|
2434
|
+
client_one.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
|
2435
|
+
end
|
2436
|
+
|
2437
|
+
context 'and the resume flag is true' do
|
2438
|
+
context 'and the presence flag is false' do
|
2439
|
+
it 'does not send any presence events as the PresenceMap is in sync (#RTP5c1)' do
|
2440
|
+
presence_client_one.enter
|
2441
|
+
presence_client_one.subscribe(:enter) do
|
2442
|
+
presence_client_one.unsubscribe :enter
|
2443
|
+
|
2444
|
+
client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
|
2445
|
+
raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
|
2446
|
+
end
|
2447
|
+
|
2448
|
+
cripple_websocket_transport
|
2449
|
+
|
2450
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2451
|
+
action: attached_action,
|
2452
|
+
channel: channel_name,
|
2453
|
+
flags: resume_flag
|
2454
|
+
)
|
2455
|
+
|
2456
|
+
EventMachine.add_timer(1) do
|
2457
|
+
presence_client_one.get do |members|
|
2458
|
+
expect(members.length).to eql(1)
|
2459
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2460
|
+
stop_reactor
|
2461
|
+
end
|
2462
|
+
end
|
2463
|
+
end
|
2464
|
+
end
|
2465
|
+
end
|
2466
|
+
|
2467
|
+
context 'and the presence flag is true' do
|
2468
|
+
context 'and following the SYNC all local MemberMap members are present in the PresenceMap' do
|
2469
|
+
it 'does nothing as MemberMap is in sync (#RTP5c2)' do
|
2470
|
+
presence_client_one.enter
|
2471
|
+
presence_client_one.subscribe(:enter) do
|
2472
|
+
presence_client_one.unsubscribe :enter
|
2473
|
+
|
2474
|
+
expect(presence_client_one.members.length).to eql(1)
|
2475
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2476
|
+
|
2477
|
+
presence_client_one.members.once(:in_sync) do
|
2478
|
+
presence_client_one.get do |members|
|
2479
|
+
expect(members.length).to eql(1)
|
2480
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2481
|
+
stop_reactor
|
2482
|
+
end
|
2483
|
+
end
|
2484
|
+
|
2485
|
+
client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
|
2486
|
+
raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
|
2487
|
+
end
|
2488
|
+
|
2489
|
+
cripple_websocket_transport
|
2490
|
+
|
2491
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2492
|
+
action: attached_action,
|
2493
|
+
channel: channel_name,
|
2494
|
+
flags: resume_flag + presence_flag
|
2495
|
+
)
|
2496
|
+
|
2497
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2498
|
+
action: sync_action,
|
2499
|
+
channel: channel_name,
|
2500
|
+
presence: presence_client_one.members.map(&:shallow_clone).map(&:as_json),
|
2501
|
+
channelSerial: nil # no further SYNC messages expected
|
2502
|
+
)
|
2503
|
+
end
|
2504
|
+
end
|
2505
|
+
end
|
2506
|
+
|
2507
|
+
context 'and following the SYNC a local MemberMap member is not present in the PresenceMap' do
|
2508
|
+
it 're-enters the missing members automatically (#RTP5c2)' do
|
2509
|
+
sync_check_completed = false
|
2510
|
+
|
2511
|
+
presence_client_one.enter
|
2512
|
+
presence_client_one.subscribe(:enter) do
|
2513
|
+
presence_client_one.unsubscribe :enter
|
2514
|
+
|
2515
|
+
expect(presence_client_one.members.length).to eql(1)
|
2516
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2517
|
+
|
2518
|
+
client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |message|
|
2519
|
+
next if message.action == :close # ignore finalization of connection
|
2520
|
+
|
2521
|
+
expect(message.action).to eq(:presence)
|
2522
|
+
presence_message = message.presence.first
|
2523
|
+
expect(presence_message.action).to eq(:enter)
|
2524
|
+
expect(presence_message.client_id).to eq(client_one.auth.client_id)
|
2525
|
+
|
2526
|
+
presence_client_one.subscribe(:enter) do |message|
|
2527
|
+
expect(message.connection_id).to eql(client_one.connection.id)
|
2528
|
+
expect(message.client_id).to eq(client_one.auth.client_id)
|
2529
|
+
|
2530
|
+
EventMachine.next_tick do
|
2531
|
+
expect(presence_client_one.members.length).to eql(2)
|
2532
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2533
|
+
expect(sync_check_completed).to be_truthy
|
2534
|
+
stop_reactor
|
2535
|
+
end
|
2536
|
+
end
|
2537
|
+
|
2538
|
+
# Fabricate Ably sending back the Enter PresenceMessage to the client a short while after
|
2539
|
+
# ensuring the PresenceMap for a short period does not have this member as to be expected in reality
|
2540
|
+
EventMachine.add_timer(0.2) do
|
2541
|
+
connection_id = random_str
|
2542
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2543
|
+
action: presence_action,
|
2544
|
+
channel: channel_name,
|
2545
|
+
connectionId: client_one.connection.id,
|
2546
|
+
connectionSerial: 50,
|
2547
|
+
timestamp: as_since_epoch(Time.now),
|
2548
|
+
presence: [presence_message.shallow_clone(id: "#{client_one.connection.id}:0:0", timestamp: as_since_epoch(Time.now)).as_json]
|
2549
|
+
)
|
2550
|
+
end
|
2551
|
+
end
|
2552
|
+
|
2553
|
+
presence_client_one.members.once(:in_sync) do
|
2554
|
+
# For a brief period, the client will have re-entered the missing members from the local_members
|
2555
|
+
# but the enter from Ably will have not been received, so at this point the local_members will be empty
|
2556
|
+
presence_client_one.get do |members|
|
2557
|
+
expect(members.length).to eql(1)
|
2558
|
+
expect(members.first.connection_id).to_not eql(client_one.connection.id)
|
2559
|
+
expect(presence_client_one.members.local_members.length).to eql(0)
|
2560
|
+
sync_check_completed = true
|
2561
|
+
end
|
2562
|
+
end
|
2563
|
+
|
2564
|
+
cripple_websocket_transport
|
2565
|
+
|
2566
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2567
|
+
action: attached_action,
|
2568
|
+
channel: channel_name,
|
2569
|
+
flags: resume_flag + presence_flag
|
2570
|
+
)
|
2571
|
+
|
2572
|
+
# Complete the SYNC but without the member who was entered by this client
|
2573
|
+
connection_id = random_str
|
2574
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2575
|
+
action: sync_action,
|
2576
|
+
channel: channel_name,
|
2577
|
+
timestamp: as_since_epoch(Time.now),
|
2578
|
+
presence: [{ id: "#{connection_id}:0:0", action: present_action, connection_id: connection_id, client_id: random_str }],
|
2579
|
+
chanenlSerial: nil # no further SYNC messages expected
|
2580
|
+
)
|
2581
|
+
end
|
2582
|
+
end
|
2583
|
+
end
|
2584
|
+
end
|
2585
|
+
end
|
2586
|
+
|
2587
|
+
context 'and the resume flag is false' do
|
2588
|
+
context 'and the presence flag is false' do
|
2589
|
+
let(:member_data) { random_str }
|
2590
|
+
|
2591
|
+
it 'immediately resends all local presence members (#RTP5c2, #RTP19a)' do
|
2592
|
+
in_sync_confirmed_no_local_members = false
|
2593
|
+
local_member_leave_event_fired = false
|
2594
|
+
|
2595
|
+
presence_client_one.enter(member_data)
|
2596
|
+
presence_client_one.subscribe(:enter) do
|
2597
|
+
presence_client_one.unsubscribe :enter
|
2598
|
+
|
2599
|
+
presence_client_one.subscribe(:leave) do |message|
|
2600
|
+
# The local member will leave the PresenceMap due to the ATTACHED without Presence
|
2601
|
+
local_member_leave_event_fired = true
|
2602
|
+
end
|
2603
|
+
|
2604
|
+
# Local members re-entered automatically appear as updates due to the
|
2605
|
+
# fabricated ATTACHED message sent and the members already being present
|
2606
|
+
presence_client_one.subscribe(:update) do |message|
|
2607
|
+
expect(local_member_leave_event_fired).to be_truthy
|
2608
|
+
expect(message.data).to eq(member_data)
|
2609
|
+
expect(message.client_id).to eq(client_one.auth.client_id)
|
2610
|
+
EventMachine.next_tick do
|
2611
|
+
expect(presence_client_one.members.length).to eql(1)
|
2612
|
+
expect(presence_client_one.members.local_members.length).to eql(1)
|
2613
|
+
expect(in_sync_confirmed_no_local_members).to be_truthy
|
2614
|
+
stop_reactor
|
2615
|
+
end
|
2616
|
+
end
|
2617
|
+
|
2618
|
+
presence_client_one.members.once(:in_sync) do
|
2619
|
+
# Immediately after SYNC (no sync actually occurred, but this event fires immediately after a channel SYNCs or is not expecting to SYNC)
|
2620
|
+
expect(presence_client_one.members.length).to eql(0)
|
2621
|
+
expect(presence_client_one.members.local_members.length).to eql(0)
|
2622
|
+
in_sync_confirmed_no_local_members = true
|
2623
|
+
end
|
2624
|
+
|
2625
|
+
# ATTACHED ProtocolMessage with no presence flag will clear the presence set immediately, #RTP19a
|
2626
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2627
|
+
action: attached_action,
|
2628
|
+
channel: channel_name,
|
2629
|
+
flags: 0 # no resume or presence flag
|
2630
|
+
)
|
2631
|
+
end
|
2632
|
+
end
|
2633
|
+
end
|
2634
|
+
end
|
2635
|
+
|
2636
|
+
context 'when re-entering a client automatically, if the re-enter fails for any reason' do
|
2637
|
+
let(:client_one_options) do
|
2638
|
+
client_options.merge(client_id: client_one_id, log_level: :error)
|
2639
|
+
end
|
2640
|
+
let(:client_one) { auto_close Ably::Realtime::Client.new(client_one_options) }
|
2641
|
+
|
2642
|
+
it 'should emit an ErrorInfo with error code 91004 (#RTP5c3)' do
|
2643
|
+
presence_client_one.enter
|
2644
|
+
|
2645
|
+
# Wait for client to be entered
|
2646
|
+
presence_client_one.subscribe(:enter) do
|
2647
|
+
# Local member should not be re-entered as the request to re-enter will timeout
|
2648
|
+
presence_client_one.subscribe(:update) do |message|
|
2649
|
+
raise "Unexpected update, this should not happen as the re-enter fails"
|
2650
|
+
end
|
2651
|
+
|
2652
|
+
channel_client_one.on(:update) do |channel_state_change|
|
2653
|
+
next if channel_state_change.reason.nil? # first update is generated by the fabricated ATTACHED
|
2654
|
+
|
2655
|
+
expect(channel_state_change.reason.code).to eql(91004)
|
2656
|
+
expect(channel_state_change.reason.message).to match(/#{client_one_id}/)
|
2657
|
+
expect(channel_state_change.reason.message).to match(/Fabricated/) # fabricated message
|
2658
|
+
expect(channel_state_change.reason.message).to match(/2345/) # fabricated error code
|
2659
|
+
stop_reactor
|
2660
|
+
end
|
2661
|
+
|
2662
|
+
cripple_websocket_transport
|
2663
|
+
|
2664
|
+
client_one.connection.__outgoing_protocol_msgbus__.subscribe do |protocol_message|
|
2665
|
+
if protocol_message.action == :presence
|
2666
|
+
# Fabricate a NACK for the re-enter message
|
2667
|
+
EventMachine.add_timer(0.1) do
|
2668
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2669
|
+
action: Ably::Models::ProtocolMessage::ACTION.Nack.to_i ,
|
2670
|
+
channel: channel_name,
|
2671
|
+
count: 1,
|
2672
|
+
msg_serial: protocol_message.message_serial,
|
2673
|
+
error: {
|
2674
|
+
message: 'Fabricated',
|
2675
|
+
code: 2345
|
2676
|
+
}
|
2677
|
+
)
|
2678
|
+
end
|
2679
|
+
end
|
2680
|
+
end
|
2681
|
+
|
2682
|
+
# ATTACHED ProtocolMessage with no presence flag will clear the presence set immediately, #RTP19a
|
2683
|
+
fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
|
2684
|
+
action: attached_action,
|
2685
|
+
channel: channel_name,
|
2686
|
+
flags: 0 # no resume or presence flag
|
2687
|
+
)
|
2688
|
+
end
|
2689
|
+
end
|
2690
|
+
end
|
2691
|
+
end
|
2692
|
+
end
|
2693
|
+
|
2694
|
+
context 'channel state side effects' do
|
2695
|
+
context 'channel transitions to the FAILED state' do
|
2696
|
+
let(:anonymous_client) { auto_close Ably::Realtime::Client.new(client_options.merge(log_level: :fatal)) }
|
2697
|
+
let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: client_one_id, log_level: :fatal)) }
|
2698
|
+
|
2699
|
+
it 'clears the PresenceMap and local member map copy and does not emit any presence events (#RTP5a)' do
|
2700
|
+
presence_client_one.enter
|
2701
|
+
presence_client_one.subscribe(:enter) do
|
2702
|
+
presence_client_one.unsubscribe :enter
|
2703
|
+
|
2704
|
+
channel_anonymous_client.attach do
|
2705
|
+
presence_anonymous_client.get do |members|
|
2706
|
+
expect(members.count).to eq(1)
|
2707
|
+
|
2708
|
+
presence_anonymous_client.subscribe { raise 'No presence events should be emitted' }
|
2709
|
+
channel_anonymous_client.transition_state_machine! :failed, reason: RuntimeError.new
|
2710
|
+
expect(presence_anonymous_client.members.length).to eq(0)
|
2711
|
+
expect(channel_anonymous_client).to be_failed
|
2712
|
+
presence_anonymous_client.unsubscribe # prevent events being sent to the channel from Ably as it is unaware it's FAILED
|
2713
|
+
|
2714
|
+
expect(presence_client_one.members.local_members.count).to eq(1)
|
2715
|
+
channel_client_one.transition_state_machine! :failed
|
2716
|
+
expect(channel_client_one).to be_failed
|
2717
|
+
expect(presence_client_one.members.local_members.count).to eq(0)
|
2718
|
+
stop_reactor
|
2719
|
+
end
|
2720
|
+
end
|
2721
|
+
end
|
2722
|
+
end
|
2723
|
+
end
|
2724
|
+
|
2725
|
+
context 'channel transitions to the DETACHED state' do
|
2726
|
+
it 'clears the PresenceMap and local member map copy and does not emit any presence events (#RTP5a)' do
|
2727
|
+
presence_client_one.enter
|
2728
|
+
presence_client_one.subscribe(:enter) do
|
2729
|
+
presence_client_one.unsubscribe :enter
|
2730
|
+
|
2731
|
+
channel_anonymous_client.attach do
|
2732
|
+
presence_anonymous_client.get do |members|
|
2733
|
+
expect(members.count).to eq(1)
|
2734
|
+
|
2735
|
+
presence_anonymous_client.subscribe { raise 'No presence events should be emitted' }
|
2736
|
+
channel_anonymous_client.detach do
|
2737
|
+
expect(presence_anonymous_client.members.length).to eq(0)
|
2738
|
+
expect(channel_anonymous_client).to be_detached
|
2739
|
+
|
2740
|
+
expect(presence_client_one.members.local_members.count).to eq(1)
|
2741
|
+
channel_client_one.detach do
|
2742
|
+
expect(presence_client_one.members.local_members.count).to eq(0)
|
2743
|
+
stop_reactor
|
2744
|
+
end
|
2745
|
+
end
|
2746
|
+
end
|
2747
|
+
end
|
2748
|
+
end
|
2749
|
+
end
|
2750
|
+
end
|
2751
|
+
|
2752
|
+
context 'channel transitions to the SUSPENDED state' do
|
2753
|
+
let(:auth_callback) do
|
2754
|
+
Proc.new do
|
2755
|
+
# Pause to allow presence updates to occur whilst disconnected
|
2756
|
+
sleep 1
|
2757
|
+
Ably::Rest::Client.new(client_options).auth.request_token
|
2758
|
+
end
|
2759
|
+
end
|
2760
|
+
let(:anonymous_client) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: auth_callback)) }
|
2761
|
+
|
2762
|
+
it 'maintains the PresenceMap and only publishes presence event changes since the last attached state (#RTP5f)' do
|
2763
|
+
presence_client_one.enter do
|
2764
|
+
presence_client_two.enter
|
2765
|
+
end
|
2766
|
+
|
2767
|
+
entered_count = 0
|
2768
|
+
presence_client_one.subscribe(:enter) do
|
2769
|
+
entered_count += 1
|
2770
|
+
next unless entered_count == 2
|
2771
|
+
|
2772
|
+
presence_client_one.unsubscribe :enter
|
2773
|
+
channel_anonymous_client.attach do
|
2774
|
+
presence_anonymous_client.get do |members|
|
2775
|
+
expect(members.count).to eq(2)
|
2776
|
+
|
2777
|
+
received_events = []
|
2778
|
+
presence_anonymous_client.subscribe do |presence_message|
|
2779
|
+
expect(presence_message.action).to eq(:leave)
|
2780
|
+
expect(presence_message.client_id).to eql(client_one_id)
|
2781
|
+
received_events << [:leave, presence_message.client_id]
|
2782
|
+
end
|
2783
|
+
|
2784
|
+
# Kill the connection triggering an automatic reconnect and reattach of the channel that is about to put into the suspended state
|
2785
|
+
anonymous_client.connection.transport.close_connection_after_writing
|
2786
|
+
|
2787
|
+
# Prevent the same connection resuming, we want a new connection and the channel SYNC to be sent
|
2788
|
+
anonymous_client.connection.reset_resume_info
|
2789
|
+
|
2790
|
+
anonymous_client.connection.once(:disconnected) do
|
2791
|
+
# Move to the SUSPENDED state and check presence map intact
|
2792
|
+
channel_anonymous_client.transition_state_machine! :suspended
|
2793
|
+
|
2794
|
+
# Change the presence map state on that channel by getting one member to leave whilst the connection for anonymous client is diconnected
|
2795
|
+
presence_client_one.leave
|
2796
|
+
|
2797
|
+
# Whilst SUSPENDED and DISCONNECTED, a get of the PresenceMap should still reveal two members
|
2798
|
+
presence_anonymous_client.get(wait_for_sync: false) do |members|
|
2799
|
+
expect(members.count).to eq(2)
|
2800
|
+
|
2801
|
+
channel_anonymous_client.once(:attached) do
|
2802
|
+
presence_anonymous_client.get do |members|
|
2803
|
+
expect(members.count).to eq(1)
|
2804
|
+
EventMachine.add_timer(0.5) do
|
2805
|
+
expect(received_events).to contain_exactly([:leave, client_one_id])
|
2806
|
+
presence_anonymous_client.unsubscribe
|
2807
|
+
stop_reactor
|
2808
|
+
end
|
2809
|
+
end
|
2810
|
+
end
|
2811
|
+
end
|
2812
|
+
end
|
2813
|
+
end
|
2814
|
+
end
|
2815
|
+
end
|
2816
|
+
end
|
2817
|
+
end
|
2818
|
+
end
|
1805
2819
|
end
|
1806
2820
|
end
|