ably-rest 0.9.3 → 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/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
|
@@ -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,8 +42,17 @@ 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
|
+
acked = false
|
|
46
|
+
received = false
|
|
45
47
|
presence_client_one.public_send(method_name.to_s.gsub(/leave|update/, 'enter'), args) do
|
|
46
|
-
|
|
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)
|
|
53
|
+
presence_client_one.unsubscribe
|
|
54
|
+
received = true
|
|
55
|
+
yield if acked & received
|
|
47
56
|
end
|
|
48
57
|
else
|
|
49
58
|
yield
|
|
@@ -56,8 +65,30 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
56
65
|
channel_client_one.attach do
|
|
57
66
|
channel_client_one.transition_state_machine :detaching
|
|
58
67
|
channel_client_one.once(:detached) do
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
61
92
|
end
|
|
62
93
|
end
|
|
63
94
|
end
|
|
@@ -68,8 +99,30 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
68
99
|
channel_client_one.attach do
|
|
69
100
|
channel_client_one.transition_state_machine :failed
|
|
70
101
|
expect(channel_client_one.state).to eq(:failed)
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
126
|
end
|
|
74
127
|
end
|
|
75
128
|
end
|
|
@@ -86,20 +139,24 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
86
139
|
let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id)) }
|
|
87
140
|
|
|
88
141
|
context 'and connection state initialized' do
|
|
89
|
-
it '
|
|
90
|
-
|
|
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
|
|
91
147
|
expect(client_one.connection).to be_initialized
|
|
92
|
-
stop_reactor
|
|
93
148
|
end
|
|
94
149
|
end
|
|
95
150
|
|
|
96
151
|
context 'and connection state connecting' do
|
|
97
|
-
it '
|
|
152
|
+
it 'fails the deferrable' do
|
|
98
153
|
client_one.connect
|
|
99
154
|
EventMachine.next_tick do
|
|
100
|
-
|
|
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
|
|
101
159
|
expect(client_one.connection).to be_connecting
|
|
102
|
-
stop_reactor
|
|
103
160
|
end
|
|
104
161
|
end
|
|
105
162
|
end
|
|
@@ -107,12 +164,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
107
164
|
context 'and connection state disconnected' do
|
|
108
165
|
let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id, :log_level => :error)) }
|
|
109
166
|
|
|
110
|
-
it '
|
|
167
|
+
it 'fails the deferrable' do
|
|
111
168
|
client_one.connection.once(:connected) do
|
|
112
169
|
client_one.connection.once(:disconnected) do
|
|
113
|
-
|
|
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
|
|
114
174
|
expect(client_one.connection).to be_disconnected
|
|
115
|
-
stop_reactor
|
|
116
175
|
end
|
|
117
176
|
client_one.connection.transition_state_machine :disconnected
|
|
118
177
|
end
|
|
@@ -258,7 +317,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
258
317
|
|
|
259
318
|
it 'catches exceptions in the provided method block and logs them to the logger' do
|
|
260
319
|
setup_test(method_name, args, options) do
|
|
261
|
-
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/)
|
|
262
322
|
stop_reactor
|
|
263
323
|
end
|
|
264
324
|
presence_client_one.public_send(method_name, args) { raise 'Intentional exception' }
|
|
@@ -321,6 +381,13 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
321
381
|
stop_reactor
|
|
322
382
|
end
|
|
323
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
|
|
324
391
|
end
|
|
325
392
|
|
|
326
393
|
context ":#{method_name} when authenticated with a valid client_id" do
|
|
@@ -409,14 +476,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
409
476
|
end
|
|
410
477
|
|
|
411
478
|
context 'when attached (but not present) on a presence channel with an anonymous client (no client ID)' do
|
|
412
|
-
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
|
|
413
480
|
channel_anonymous_client.attach do
|
|
414
481
|
presence_anonymous_client.subscribe(:enter) do |presence_message|
|
|
415
482
|
expect(presence_message.client_id).to eql(client_one.client_id)
|
|
416
483
|
|
|
417
484
|
presence_anonymous_client.get do |members|
|
|
418
485
|
expect(members.first.client_id).to eql(client_one.client_id)
|
|
419
|
-
expect(members.first.action).to eq(:
|
|
486
|
+
expect(members.first.action).to eq(:present)
|
|
420
487
|
|
|
421
488
|
presence_anonymous_client.subscribe(:leave) do |leave_presence_message|
|
|
422
489
|
expect(leave_presence_message.client_id).to eql(client_one.client_id)
|
|
@@ -436,7 +503,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
436
503
|
end
|
|
437
504
|
end
|
|
438
505
|
|
|
439
|
-
context '#members map', api_private: true do
|
|
506
|
+
context '#members map / PresenceMap (#RTP2)', api_private: true do
|
|
440
507
|
it 'is available once the channel is created' do
|
|
441
508
|
expect(presence_anonymous_client.members).to_not be_nil
|
|
442
509
|
stop_reactor
|
|
@@ -468,7 +535,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
468
535
|
|
|
469
536
|
context 'once server sync is complete' do
|
|
470
537
|
it 'behaves like an Enumerable allowing direct access to current members' do
|
|
471
|
-
|
|
538
|
+
presence_client_one.enter
|
|
539
|
+
presence_client_two.enter
|
|
540
|
+
|
|
541
|
+
entered = 0
|
|
542
|
+
presence_client_one.subscribe(:enter) do
|
|
543
|
+
entered += 1
|
|
544
|
+
next unless entered == 2
|
|
545
|
+
|
|
472
546
|
presence_anonymous_client.members.once(:in_sync) do
|
|
473
547
|
expect(presence_anonymous_client.members.count).to eql(2)
|
|
474
548
|
member_ids = presence_anonymous_client.members.map(&:member_key)
|
|
@@ -481,26 +555,199 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
481
555
|
end
|
|
482
556
|
end
|
|
483
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
|
|
484
704
|
end
|
|
485
705
|
|
|
486
|
-
context '#sync_complete?' do
|
|
706
|
+
context '#sync_complete? and SYNC flags (#RTP1)' do
|
|
487
707
|
context 'when attaching to a channel without any members present' do
|
|
488
|
-
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
|
+
|
|
489
718
|
channel_anonymous_client.attach do
|
|
490
719
|
expect(channel_anonymous_client.presence).to be_sync_complete
|
|
491
|
-
|
|
720
|
+
EventMachine.next_tick do
|
|
721
|
+
expect(flag_checked).to eql(true)
|
|
722
|
+
stop_reactor
|
|
723
|
+
end
|
|
492
724
|
end
|
|
493
725
|
end
|
|
494
726
|
end
|
|
495
727
|
|
|
496
728
|
context 'when attaching to a channel with members present' do
|
|
497
|
-
it 'is false and the presence channel
|
|
498
|
-
|
|
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
|
+
|
|
739
|
+
presence_client_one.enter
|
|
740
|
+
presence_client_one.subscribe(:enter) do
|
|
741
|
+
presence_client_one.unsubscribe :enter
|
|
742
|
+
|
|
499
743
|
channel_anonymous_client.attach do
|
|
500
744
|
expect(channel_anonymous_client.presence).to_not be_sync_complete
|
|
501
|
-
channel_anonymous_client.presence.get
|
|
745
|
+
channel_anonymous_client.presence.get do
|
|
502
746
|
expect(channel_anonymous_client.presence).to be_sync_complete
|
|
503
|
-
|
|
747
|
+
EventMachine.next_tick do
|
|
748
|
+
expect(flag_checked).to eql(true)
|
|
749
|
+
stop_reactor
|
|
750
|
+
end
|
|
504
751
|
end
|
|
505
752
|
end
|
|
506
753
|
end
|
|
@@ -508,19 +755,20 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
508
755
|
end
|
|
509
756
|
end
|
|
510
757
|
|
|
511
|
-
context '
|
|
512
|
-
context '
|
|
513
|
-
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 }
|
|
514
761
|
let(:present) { [] }
|
|
515
762
|
let(:entered) { [] }
|
|
516
763
|
let(:sync_pages_received) { [] }
|
|
517
764
|
let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
|
|
518
765
|
|
|
519
766
|
def setup_members_on(presence)
|
|
520
|
-
enter_expected_count.times do |
|
|
767
|
+
enter_expected_count.times do |indx|
|
|
521
768
|
# 10 messages per second max rate on simulation accounts
|
|
522
|
-
|
|
523
|
-
|
|
769
|
+
rate = indx.to_f / 10
|
|
770
|
+
EventMachine.add_timer(rate) do
|
|
771
|
+
presence.enter_client("client:#{indx}") do |message|
|
|
524
772
|
entered << message
|
|
525
773
|
next unless entered.count == enter_expected_count
|
|
526
774
|
yield
|
|
@@ -543,8 +791,47 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
543
791
|
end
|
|
544
792
|
end
|
|
545
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
|
+
|
|
546
833
|
context 'and a member leaves before the SYNC operation is complete' do
|
|
547
|
-
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
|
|
548
835
|
all_client_ids = enter_expected_count.times.map { |id| "client:#{id}" }
|
|
549
836
|
|
|
550
837
|
setup_members_on(presence_client_one) do
|
|
@@ -558,7 +845,14 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
558
845
|
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
|
559
846
|
expect(leave_message.client_id).to eql(leave_member.client_id)
|
|
560
847
|
expect(present.count).to be < enter_expected_count
|
|
561
|
-
|
|
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)
|
|
562
856
|
presence_anonymous_client.unsubscribe
|
|
563
857
|
stop_reactor
|
|
564
858
|
end
|
|
@@ -571,7 +865,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
571
865
|
if sync_pages_received.count == 1
|
|
572
866
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
|
573
867
|
leave_member = Ably::Models::PresenceMessage.new(
|
|
574
|
-
'id' => "#{client_one.connection.id}
|
|
868
|
+
'id' => "#{client_one.connection.id}:#{all_client_ids.first}:0",
|
|
575
869
|
'clientId' => all_client_ids.first,
|
|
576
870
|
'connectionId' => client_one.connection.id,
|
|
577
871
|
'timestamp' => as_since_epoch(Time.now),
|
|
@@ -585,7 +879,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
585
879
|
end
|
|
586
880
|
end
|
|
587
881
|
|
|
588
|
-
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
|
|
589
883
|
started_at = Time.now
|
|
590
884
|
|
|
591
885
|
setup_members_on(presence_client_one) do
|
|
@@ -593,11 +887,12 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
593
887
|
|
|
594
888
|
presence_anonymous_client.subscribe(:present) do |present_message|
|
|
595
889
|
present << present_message
|
|
596
|
-
leave_member = present_message unless leave_member
|
|
597
890
|
|
|
598
891
|
if present.count == enter_expected_count
|
|
599
892
|
presence_anonymous_client.get do |members|
|
|
600
|
-
|
|
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)
|
|
601
896
|
EventMachine.add_timer(1) do
|
|
602
897
|
presence_anonymous_client.unsubscribe
|
|
603
898
|
stop_reactor
|
|
@@ -607,7 +902,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
607
902
|
end
|
|
608
903
|
|
|
609
904
|
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
|
610
|
-
raise
|
|
905
|
+
raise "Leave event for #{leave_message} should not have been fired because it is out of date"
|
|
611
906
|
end
|
|
612
907
|
|
|
613
908
|
anonymous_client.connect do
|
|
@@ -615,10 +910,12 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
615
910
|
if protocol_message.action == :sync
|
|
616
911
|
sync_pages_received << protocol_message
|
|
617
912
|
if sync_pages_received.count == 1
|
|
913
|
+
first_member = protocol_message.presence[0] # get the first member in the SYNC set
|
|
618
914
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
|
619
915
|
leave_member = Ably::Models::PresenceMessage.new(
|
|
620
|
-
|
|
916
|
+
first_member.as_json.merge('action' => leave_action, 'timestamp' => as_since_epoch(started_at))
|
|
621
917
|
)
|
|
918
|
+
# After the SYNC has started, no inject that member has having left with a timestamp before the sync
|
|
622
919
|
presence_anonymous_client.__incoming_msgbus__.publish :presence, leave_member
|
|
623
920
|
end
|
|
624
921
|
end
|
|
@@ -627,7 +924,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
627
924
|
end
|
|
628
925
|
end
|
|
629
926
|
|
|
630
|
-
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
|
|
631
928
|
left_client = 10
|
|
632
929
|
left_client_id = "client:#{left_client}"
|
|
633
930
|
|
|
@@ -649,7 +946,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
649
946
|
member_left_emitted = true
|
|
650
947
|
end
|
|
651
948
|
|
|
652
|
-
presence_anonymous_client.get
|
|
949
|
+
presence_anonymous_client.get do |members|
|
|
653
950
|
expect(members.count).to eql(enter_expected_count - 1)
|
|
654
951
|
expect(member_left_emitted).to eql(true)
|
|
655
952
|
expect(members.map(&:client_id)).to_not include(left_client_id)
|
|
@@ -662,7 +959,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
662
959
|
channel_anonymous_client.attach do
|
|
663
960
|
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
|
664
961
|
fake_leave_presence_message = Ably::Models::PresenceMessage.new(
|
|
665
|
-
'id' => "#{client_one.connection.id}
|
|
962
|
+
'id' => "#{client_one.connection.id}:#{left_client_id}:0",
|
|
666
963
|
'clientId' => left_client_id,
|
|
667
964
|
'connectionId' => client_one.connection.id,
|
|
668
965
|
'timestamp' => as_since_epoch(Time.now),
|
|
@@ -676,35 +973,38 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
676
973
|
end
|
|
677
974
|
|
|
678
975
|
context '#get' do
|
|
679
|
-
context '
|
|
680
|
-
it 'waits until sync is complete', em_timeout: 30 do # allow for slow connections and lots of messages
|
|
681
|
-
enter_expected_count.times do |
|
|
682
|
-
EventMachine.add_timer(
|
|
683
|
-
presence_client_one.enter_client
|
|
684
|
-
|
|
685
|
-
|
|
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}"
|
|
981
|
+
end
|
|
982
|
+
end
|
|
686
983
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
984
|
+
presence_client_one.subscribe(:enter) do |message|
|
|
985
|
+
entered << message
|
|
986
|
+
next unless entered.count == enter_expected_count
|
|
987
|
+
|
|
988
|
+
presence_anonymous_client.get do |members|
|
|
989
|
+
expect(members.map(&:client_id).uniq.count).to eql(enter_expected_count)
|
|
990
|
+
expect(members.count).to eql(enter_expected_count)
|
|
991
|
+
stop_reactor
|
|
693
992
|
end
|
|
694
993
|
end
|
|
695
994
|
end
|
|
696
995
|
end
|
|
697
996
|
|
|
698
|
-
context '
|
|
997
|
+
context 'with :wait_for_sync option set to false (#RTP11c1)' do
|
|
699
998
|
it 'it does not wait for sync', em_timeout: 30 do # allow for slow connections and lots of messages
|
|
700
|
-
enter_expected_count.times do |
|
|
701
|
-
EventMachine.add_timer(
|
|
702
|
-
presence_client_one.enter_client
|
|
999
|
+
enter_expected_count.times do |indx|
|
|
1000
|
+
EventMachine.add_timer(indx / 10) do
|
|
1001
|
+
presence_client_one.enter_client "client:#{indx}"
|
|
1002
|
+
presence_client_one.subscribe(:enter) do |message|
|
|
703
1003
|
entered << message
|
|
704
1004
|
next unless entered.count == enter_expected_count
|
|
705
1005
|
|
|
706
1006
|
channel_anonymous_client.attach do
|
|
707
|
-
presence_anonymous_client.get do |members|
|
|
1007
|
+
presence_anonymous_client.get(wait_for_sync: false) do |members|
|
|
708
1008
|
expect(presence_anonymous_client.members).to_not be_in_sync
|
|
709
1009
|
expect(members.count).to eql(0)
|
|
710
1010
|
stop_reactor
|
|
@@ -896,24 +1196,41 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
896
1196
|
|
|
897
1197
|
context 'and sync is complete' do
|
|
898
1198
|
it 'does not cache members that have left' do
|
|
899
|
-
|
|
1199
|
+
enter_ack = false
|
|
1200
|
+
|
|
1201
|
+
presence_client_one.subscribe(:enter) do
|
|
1202
|
+
presence_client_one.unsubscribe :enter
|
|
1203
|
+
|
|
900
1204
|
expect(presence_client_one.members).to be_in_sync
|
|
901
1205
|
expect(presence_client_one.members.send(:members).count).to eql(1)
|
|
902
1206
|
presence_client_one.leave data
|
|
903
1207
|
end
|
|
904
1208
|
|
|
1209
|
+
presence_client_one.enter(enter_data) do
|
|
1210
|
+
enter_ack = true
|
|
1211
|
+
end
|
|
1212
|
+
|
|
905
1213
|
presence_client_one.subscribe(:leave) do |presence_message|
|
|
1214
|
+
presence_client_one.unsubscribe :leave
|
|
906
1215
|
expect(presence_message.data).to eql(data)
|
|
907
1216
|
expect(presence_client_one.members.send(:members).count).to eql(0)
|
|
1217
|
+
expect(enter_ack).to eql(true)
|
|
908
1218
|
stop_reactor
|
|
909
1219
|
end
|
|
910
1220
|
end
|
|
911
1221
|
end
|
|
912
1222
|
end
|
|
913
1223
|
|
|
914
|
-
it '
|
|
915
|
-
|
|
916
|
-
|
|
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
|
|
917
1234
|
end
|
|
918
1235
|
|
|
919
1236
|
it_should_behave_like 'a public presence method', :leave, :left, {}, enter_first: true
|
|
@@ -1193,28 +1510,75 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1193
1510
|
end
|
|
1194
1511
|
|
|
1195
1512
|
it 'catches exceptions in the provided method block' do
|
|
1196
|
-
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/)
|
|
1197
1515
|
stop_reactor
|
|
1198
1516
|
end
|
|
1199
1517
|
presence_client_one.get { raise 'Intentional exception' }
|
|
1200
1518
|
end
|
|
1201
1519
|
|
|
1202
|
-
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
|
|
1203
1556
|
channel_client_one.attach do
|
|
1204
|
-
channel_client_one.
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
1208
1566
|
end
|
|
1209
1567
|
end
|
|
1210
1568
|
end
|
|
1211
1569
|
|
|
1212
|
-
it '
|
|
1570
|
+
it 'fails if the connection is FAILED (#RTP11b)' do
|
|
1213
1571
|
channel_client_one.attach do
|
|
1214
1572
|
channel_client_one.transition_state_machine :failed
|
|
1215
1573
|
expect(channel_client_one.state).to eq(:failed)
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
|
1218
1582
|
end
|
|
1219
1583
|
end
|
|
1220
1584
|
|
|
@@ -1240,7 +1604,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1240
1604
|
end
|
|
1241
1605
|
|
|
1242
1606
|
context 'when :wait_for_sync is true' do
|
|
1243
|
-
it 'fails if the connection
|
|
1607
|
+
it 'fails if the connection becomes FAILED (#RTP11b)' do
|
|
1244
1608
|
when_all(*connect_members_deferrables) do
|
|
1245
1609
|
channel_client_two.attach do
|
|
1246
1610
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
@@ -1263,7 +1627,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1263
1627
|
end
|
|
1264
1628
|
end
|
|
1265
1629
|
|
|
1266
|
-
it 'fails if the channel
|
|
1630
|
+
it 'fails if the channel becomes detached (#RTP11b)' do
|
|
1267
1631
|
when_all(*connect_members_deferrables) do
|
|
1268
1632
|
channel_client_two.attach do
|
|
1269
1633
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
@@ -1287,10 +1651,10 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1287
1651
|
end
|
|
1288
1652
|
end
|
|
1289
1653
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1654
|
+
it 'returns the current members on the channel (#RTP11a)' do
|
|
1655
|
+
presence_client_one.enter
|
|
1656
|
+
presence_client_one.subscribe(:enter) do
|
|
1657
|
+
presence_client_one.unsubscribe :enter
|
|
1294
1658
|
presence_client_one.get do |members|
|
|
1295
1659
|
expect(members.count).to eq(1)
|
|
1296
1660
|
|
|
@@ -1304,7 +1668,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1304
1668
|
end
|
|
1305
1669
|
end
|
|
1306
1670
|
|
|
1307
|
-
it 'filters by connection_id option if provided' do
|
|
1671
|
+
it 'filters by connection_id option if provided (#RTP11c3)' do
|
|
1308
1672
|
presence_client_one.enter do
|
|
1309
1673
|
presence_client_two.enter
|
|
1310
1674
|
end
|
|
@@ -1326,7 +1690,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1326
1690
|
end
|
|
1327
1691
|
end
|
|
1328
1692
|
|
|
1329
|
-
it 'filters by client_id option if provided' do
|
|
1693
|
+
it 'filters by client_id option if provided (#RTP11c2)' do
|
|
1330
1694
|
presence_client_one.enter do
|
|
1331
1695
|
presence_client_two.enter
|
|
1332
1696
|
end
|
|
@@ -1350,8 +1714,11 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1350
1714
|
end
|
|
1351
1715
|
end
|
|
1352
1716
|
|
|
1353
|
-
it 'does not wait for SYNC to complete if :wait_for_sync option is false' do
|
|
1354
|
-
presence_client_one.enter
|
|
1717
|
+
it 'does not wait for SYNC to complete if :wait_for_sync option is false (#RTP11c1)' do
|
|
1718
|
+
presence_client_one.enter
|
|
1719
|
+
presence_client_one.subscribe(:enter) do
|
|
1720
|
+
presence_client_one.unsubscribe :enter
|
|
1721
|
+
|
|
1355
1722
|
presence_client_two.get(wait_for_sync: false) do |members|
|
|
1356
1723
|
expect(members.count).to eql(0)
|
|
1357
1724
|
stop_reactor
|
|
@@ -1359,14 +1726,43 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1359
1726
|
end
|
|
1360
1727
|
end
|
|
1361
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
|
+
|
|
1362
1741
|
context 'when a member enters and then leaves' do
|
|
1363
1742
|
it 'has no members' do
|
|
1364
1743
|
presence_client_one.enter do
|
|
1365
|
-
presence_client_one.leave
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1744
|
+
presence_client_one.leave
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
presence_client_one.subscribe(:leave) do
|
|
1748
|
+
presence_client_one.get do |members|
|
|
1749
|
+
expect(members.count).to eq(0)
|
|
1750
|
+
stop_reactor
|
|
1751
|
+
end
|
|
1752
|
+
end
|
|
1753
|
+
end
|
|
1754
|
+
end
|
|
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
|
|
1370
1766
|
end
|
|
1371
1767
|
end
|
|
1372
1768
|
end
|
|
@@ -1380,9 +1776,9 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1380
1776
|
let(:total_members) { members_per_client * 2 }
|
|
1381
1777
|
|
|
1382
1778
|
it 'returns a complete list of members on all clients' do
|
|
1383
|
-
members_per_client.times do |
|
|
1384
|
-
presence_client_one.enter_client("client_1:#{
|
|
1385
|
-
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}")
|
|
1386
1782
|
end
|
|
1387
1783
|
|
|
1388
1784
|
presence_client_one.subscribe(:enter) do
|
|
@@ -1469,7 +1865,9 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1469
1865
|
|
|
1470
1866
|
it 'logs the error and continues' do
|
|
1471
1867
|
emitted_exception = false
|
|
1472
|
-
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
|
|
1473
1871
|
presence_client_one.subscribe do |presence_message|
|
|
1474
1872
|
emitted_exception = true
|
|
1475
1873
|
raise exception
|
|
@@ -1524,7 +1922,10 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1524
1922
|
|
|
1525
1923
|
context 'REST #get' do
|
|
1526
1924
|
it 'returns current members' do
|
|
1527
|
-
presence_client_one.enter
|
|
1925
|
+
presence_client_one.enter data_payload
|
|
1926
|
+
presence_client_one.subscribe(:enter) do
|
|
1927
|
+
presence_client_one.unsubscribe :enter
|
|
1928
|
+
|
|
1528
1929
|
members_page = channel_rest_client_one.presence.get
|
|
1529
1930
|
this_member = members_page.items.first
|
|
1530
1931
|
|
|
@@ -1538,7 +1939,10 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1538
1939
|
|
|
1539
1940
|
it 'returns no members once left' do
|
|
1540
1941
|
presence_client_one.enter(data_payload) do
|
|
1541
|
-
presence_client_one.leave
|
|
1942
|
+
presence_client_one.leave
|
|
1943
|
+
presence_client_one.subscribe(:leave) do
|
|
1944
|
+
presence_client_one.unsubscribe :leave
|
|
1945
|
+
|
|
1542
1946
|
members_page = channel_rest_client_one.presence.get
|
|
1543
1947
|
expect(members_page.items.count).to eql(0)
|
|
1544
1948
|
stop_reactor
|
|
@@ -1654,7 +2058,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1654
2058
|
|
|
1655
2059
|
context '#get' do
|
|
1656
2060
|
it 'returns a list of members with decrypted data' do
|
|
1657
|
-
encrypted_channel.presence.enter(data)
|
|
2061
|
+
encrypted_channel.presence.enter(data)
|
|
2062
|
+
encrypted_channel.presence.subscribe(:enter) do
|
|
1658
2063
|
encrypted_channel.presence.get do |members|
|
|
1659
2064
|
member = members.first
|
|
1660
2065
|
expect(member.encoding).to be_nil
|
|
@@ -1667,7 +2072,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1667
2072
|
|
|
1668
2073
|
context 'REST #get' do
|
|
1669
2074
|
it 'returns a list of members with decrypted data' do
|
|
1670
|
-
encrypted_channel.presence.enter(data)
|
|
2075
|
+
encrypted_channel.presence.enter(data)
|
|
2076
|
+
encrypted_channel.presence.subscribe(:enter) do
|
|
1671
2077
|
member = channel_rest_client_one.presence.get.items.first
|
|
1672
2078
|
expect(member.encoding).to be_nil
|
|
1673
2079
|
expect(member.data).to eql(data)
|
|
@@ -1696,9 +2102,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1696
2102
|
|
|
1697
2103
|
it 'emits an error when cipher does not match and presence data cannot be decoded' do
|
|
1698
2104
|
incompatible_encrypted_channel.attach do
|
|
1699
|
-
|
|
1700
|
-
expect(
|
|
1701
|
-
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/)
|
|
1702
2107
|
stop_reactor
|
|
1703
2108
|
end
|
|
1704
2109
|
|
|
@@ -1736,13 +2141,13 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1736
2141
|
end
|
|
1737
2142
|
|
|
1738
2143
|
context 'connection failure mid-way through a large member sync' do
|
|
1739
|
-
let(:members_count) {
|
|
2144
|
+
let(:members_count) { 201 }
|
|
1740
2145
|
let(:sync_pages_received) { [] }
|
|
1741
|
-
let(:client_options) { default_options.merge(log_level: :
|
|
2146
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
|
1742
2147
|
|
|
1743
|
-
it 'resumes the SYNC operation', em_timeout: 15 do
|
|
1744
|
-
when_all(*members_count.times.map do |
|
|
1745
|
-
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}")
|
|
1746
2151
|
end) do
|
|
1747
2152
|
channel_client_two.attach do
|
|
1748
2153
|
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
|
@@ -1761,5 +2166,655 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
|
1761
2166
|
end
|
|
1762
2167
|
end
|
|
1763
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
|
|
1764
2819
|
end
|
|
1765
2820
|
end
|