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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
@@ -6,7 +6,7 @@ describe Ably::Models::ConnectionStateChange do
6
6
 
7
7
  subject { Ably::Models::ConnectionStateChange }
8
8
 
9
- context '#current' do
9
+ context '#current (#TA2)' do
10
10
  it 'is required' do
11
11
  expect { subject.new(previous: true) }.to raise_error ArgumentError
12
12
  end
@@ -16,7 +16,7 @@ describe Ably::Models::ConnectionStateChange do
16
16
  end
17
17
  end
18
18
 
19
- context '#previous' do
19
+ context '#previous(#TA2)' do
20
20
  it 'is required' do
21
21
  expect { subject.new(current: true) }.to raise_error ArgumentError
22
22
  end
@@ -26,7 +26,18 @@ describe Ably::Models::ConnectionStateChange do
26
26
  end
27
27
  end
28
28
 
29
- context '#retry_in' do
29
+ context '#event(#TA5)' do
30
+ it 'is not required' do
31
+ expect { subject.new(previous: true, current: true) }.to_not raise_error
32
+ end
33
+
34
+ it 'is an attribute' do
35
+ expect(subject.new(event: unique, current: true, previous: true).event).to eql(unique)
36
+ end
37
+ end
38
+
39
+
40
+ context '#retry_in (#TA2)' do
30
41
  it 'is not required' do
31
42
  expect { subject.new(previous: true, current: true) }.to_not raise_error
32
43
  end
@@ -36,7 +47,7 @@ describe Ably::Models::ConnectionStateChange do
36
47
  end
37
48
  end
38
49
 
39
- context '#reason' do
50
+ context '#reason (#TA3)' do
40
51
  it 'is not required' do
41
52
  expect { subject.new(previous: true, current: true) }.to_not raise_error
42
53
  end
@@ -57,8 +57,9 @@ describe Ably::Models::MessageEncoders::Base64 do
57
57
 
58
58
  context '#encode' do
59
59
  context 'over binary transport' do
60
+ subject { Ably::Models::MessageEncoders::Base64.new(client, binary_protocol: true) }
61
+
60
62
  before do
61
- allow(client).to receive(:protocol_binary?).and_return(true)
62
63
  subject.encode message, {}
63
64
  end
64
65
 
@@ -25,6 +25,38 @@ describe Ably::Models::Message do
25
25
  end
26
26
  end
27
27
 
28
+ context '#extras (#TM2i)' do
29
+ let(:model) { subject.new({ extras: extras }, protocol_message: protocol_message) }
30
+
31
+ context 'when missing' do
32
+ let(:model) { subject.new({}, protocol_message: protocol_message) }
33
+ it 'is nil' do
34
+ expect(model.extras).to be_nil
35
+ end
36
+ end
37
+
38
+ context 'when a string' do
39
+ let(:extras) { 'string' }
40
+ it 'raises an exception' do
41
+ expect { model.extras }.to raise_error ArgumentError, /extras contains an unsupported type/
42
+ end
43
+ end
44
+
45
+ context 'when a Hash' do
46
+ let(:extras) { { key: 'value' } }
47
+ it 'contains a Hash Json object' do
48
+ expect(model.extras).to eq(extras)
49
+ end
50
+ end
51
+
52
+ context 'when a Json Array' do
53
+ let(:extras) { [{ 'key' => 'value' }] }
54
+ it 'contains a Json Array object' do
55
+ expect(model.extras).to eq(extras)
56
+ end
57
+ end
58
+ end
59
+
28
60
  context '#connection_id attribute' do
29
61
  let(:protocol_connection_id) { random_str }
30
62
  let(:protocol_message) { Ably::Models::ProtocolMessage.new('connectionId' => protocol_connection_id, action: 1, timestamp: protocol_message_timestamp) }
@@ -379,4 +411,125 @@ describe Ably::Models::Message do
379
411
  end
380
412
  end
381
413
  end
414
+
415
+ context '#from_encoded (#TM3)' do
416
+ context 'with no encoding' do
417
+ let(:message_data) do
418
+ { name: 'name', data: 'data-string' }
419
+ end
420
+ let(:from_encoded) { subject.from_encoded(message_data) }
421
+
422
+ it 'returns a message object' do
423
+ expect(from_encoded).to be_a(Ably::Models::Message)
424
+ expect(from_encoded.name).to eql('name')
425
+ expect(from_encoded.data).to eql('data-string')
426
+ expect(from_encoded.encoding).to be_nil
427
+ end
428
+
429
+ context 'with a block' do
430
+ it 'does not call the block' do
431
+ block_called = false
432
+ subject.from_encoded(message_data) do |exception, message|
433
+ block_called = true
434
+ end
435
+ expect(block_called).to be_falsey
436
+ end
437
+ end
438
+ end
439
+
440
+ context 'with an encoding' do
441
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
442
+ let(:message_data) do
443
+ { name: 'name', data: JSON.dump(hash_data), encoding: 'json' }
444
+ end
445
+ let(:from_encoded) { subject.from_encoded(message_data) }
446
+
447
+ it 'returns a message object' do
448
+ expect(from_encoded).to be_a(Ably::Models::Message)
449
+ expect(from_encoded.name).to eql('name')
450
+ expect(from_encoded.data).to eql(hash_data)
451
+ expect(from_encoded.encoding).to be_nil
452
+ end
453
+ end
454
+
455
+ context 'with a custom encoding' do
456
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
457
+ let(:message_data) do
458
+ { name: 'name', data: JSON.dump(hash_data), encoding: 'foo/json' }
459
+ end
460
+ let(:from_encoded) { subject.from_encoded(message_data) }
461
+
462
+ it 'returns a message object with the residual incompatible transforms left in the encoding property' do
463
+ expect(from_encoded).to be_a(Ably::Models::Message)
464
+ expect(from_encoded.name).to eql('name')
465
+ expect(from_encoded.data).to eql(hash_data)
466
+ expect(from_encoded.encoding).to eql('foo')
467
+ end
468
+ end
469
+
470
+ context 'with a Cipher encoding' do
471
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
472
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
473
+ let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
474
+ let(:payload) { random_str }
475
+ let(:message_data) do
476
+ { name: 'name', data: crypto.encrypt(payload), encoding: 'utf-8/cipher+aes-128-cbc' }
477
+ end
478
+ let(:channel_options) { { cipher: cipher_params } }
479
+ let(:from_encoded) { subject.from_encoded(message_data, channel_options) }
480
+
481
+ it 'returns a message object with the residual incompatible transforms left in the encoding property' do
482
+ expect(from_encoded).to be_a(Ably::Models::Message)
483
+ expect(from_encoded.name).to eql('name')
484
+ expect(from_encoded.data).to eql(payload)
485
+ expect(from_encoded.encoding).to be_nil
486
+ end
487
+ end
488
+
489
+ context 'with invalid Cipher encoding' do
490
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
491
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
492
+ let(:unencryped_payload) { random_str }
493
+ let(:message_data) do
494
+ { name: 'name', data: unencryped_payload, encoding: 'utf-8/cipher+aes-128-cbc' }
495
+ end
496
+ let(:channel_options) { { cipher: cipher_params } }
497
+
498
+ context 'without a block' do
499
+ it 'raises an exception' do
500
+ expect { subject.from_encoded(message_data, channel_options) }.to raise_exception(Ably::Exceptions::CipherError)
501
+ end
502
+ end
503
+
504
+ context 'with a block' do
505
+ it 'calls the block with the exception' do
506
+ block_called = false
507
+ subject.from_encoded(message_data, channel_options) do |exception, message|
508
+ expect(exception).to be_a(Ably::Exceptions::CipherError)
509
+ block_called = true
510
+ end
511
+ expect(block_called).to be_truthy
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ context '#from_encoded_array (#TM3)' do
518
+ context 'with no encoding' do
519
+ let(:message_data) do
520
+ [{ name: 'name1', data: 'data-string' }, { name: 'name2', data: 'data-string' }]
521
+ end
522
+ let(:from_encoded) { subject.from_encoded_array(message_data) }
523
+
524
+ it 'returns an Array of message objects' do
525
+ first = from_encoded.first
526
+ expect(first).to be_a(Ably::Models::Message)
527
+ expect(first.name).to eql('name1')
528
+ expect(first.data).to eql('data-string')
529
+ expect(first.encoding).to be_nil
530
+ last = from_encoded.last
531
+ expect(last.name).to eql('name2')
532
+ end
533
+ end
534
+ end
382
535
  end
@@ -383,4 +383,196 @@ describe Ably::Models::PresenceMessage do
383
383
  end
384
384
  end
385
385
  end
386
+
387
+
388
+ context '#from_encoded (#TP4)' do
389
+ context 'with no encoding' do
390
+ let(:message_data) do
391
+ { action: 2, data: 'data-string' }
392
+ end
393
+ let(:from_encoded) { subject.from_encoded(message_data) }
394
+
395
+ it 'returns a presence message object' do
396
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
397
+ expect(from_encoded.action).to eq(:enter)
398
+ expect(from_encoded.data).to eql('data-string')
399
+ expect(from_encoded.encoding).to be_nil
400
+ end
401
+
402
+ context 'with a block' do
403
+ it 'does not call the block' do
404
+ block_called = false
405
+ subject.from_encoded(message_data) do |exception, message|
406
+ block_called = true
407
+ end
408
+ expect(block_called).to be_falsey
409
+ end
410
+ end
411
+ end
412
+
413
+ context 'with an encoding' do
414
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
415
+ let(:message_data) do
416
+ { action: 'leave', data: JSON.dump(hash_data), encoding: 'json' }
417
+ end
418
+ let(:from_encoded) { subject.from_encoded(message_data) }
419
+
420
+ it 'returns a presence message object' do
421
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
422
+ expect(from_encoded.action).to eq(:leave)
423
+ expect(from_encoded.data).to eql(hash_data)
424
+ expect(from_encoded.encoding).to be_nil
425
+ end
426
+ end
427
+
428
+ context 'with a custom encoding' do
429
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
430
+ let(:message_data) do
431
+ { action: 1, data: JSON.dump(hash_data), encoding: 'foo/json' }
432
+ end
433
+ let(:from_encoded) { subject.from_encoded(message_data) }
434
+
435
+ it 'returns a presence message object with the residual incompatible transforms left in the encoding property' do
436
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
437
+ expect(from_encoded.action).to eq(1)
438
+ expect(from_encoded.data).to eql(hash_data)
439
+ expect(from_encoded.encoding).to eql('foo')
440
+ end
441
+ end
442
+
443
+ context 'with a Cipher encoding' do
444
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
445
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
446
+ let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
447
+ let(:payload) { random_str }
448
+ let(:message_data) do
449
+ { action: 1, data: crypto.encrypt(payload), encoding: 'utf-8/cipher+aes-128-cbc' }
450
+ end
451
+ let(:channel_options) { { cipher: cipher_params } }
452
+ let(:from_encoded) { subject.from_encoded(message_data, channel_options) }
453
+
454
+ it 'returns a presence message object with the residual incompatible transforms left in the encoding property' do
455
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
456
+ expect(from_encoded.data).to eql(payload)
457
+ expect(from_encoded.encoding).to be_nil
458
+ end
459
+ end
460
+
461
+ context 'with invalid Cipher encoding' do
462
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
463
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
464
+ let(:unencryped_payload) { random_str }
465
+ let(:message_data) do
466
+ { action: 1, data: unencryped_payload, encoding: 'utf-8/cipher+aes-128-cbc' }
467
+ end
468
+ let(:channel_options) { { cipher: cipher_params } }
469
+
470
+ context 'without a block' do
471
+ it 'raises an exception' do
472
+ expect { subject.from_encoded(message_data, channel_options) }.to raise_exception(Ably::Exceptions::CipherError)
473
+ end
474
+ end
475
+
476
+ context 'with a block' do
477
+ it 'calls the block with the exception' do
478
+ block_called = false
479
+ subject.from_encoded(message_data, channel_options) do |exception, message|
480
+ expect(exception).to be_a(Ably::Exceptions::CipherError)
481
+ block_called = true
482
+ end
483
+ expect(block_called).to be_truthy
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ context '#from_encoded_array (#TP4)' do
490
+ context 'with no encoding' do
491
+ let(:message_data) do
492
+ [{ action: 1, data: 'data-string' }, { action: 2, data: 'data-string' }]
493
+ end
494
+ let(:from_encoded) { subject.from_encoded_array(message_data) }
495
+
496
+ it 'returns an Array of presence message objects' do
497
+ first = from_encoded.first
498
+ expect(first).to be_a(Ably::Models::PresenceMessage)
499
+ expect(first.action).to eq(1)
500
+ expect(first.data).to eql('data-string')
501
+ expect(first.encoding).to be_nil
502
+ last = from_encoded.last
503
+ expect(last.action).to eq(:enter)
504
+ end
505
+ end
506
+ end
507
+
508
+ context '#shallow_clone' do
509
+ context 'with inherited attributes from ProtocolMessage' do
510
+ let(:protocol_message) {
511
+ Ably::Models::ProtocolMessage.new('id' => 'fooId', 'connectionId' => protocol_connection_id, 'action' => 1, 'timestamp' => protocol_message_timestamp)
512
+ }
513
+ let(:protocol_connection_id) { random_str }
514
+ let(:model) { subject.new({ 'action' => 2 }, protocol_message: protocol_message) }
515
+
516
+ it 'creates a duplicate of the message without any ProtocolMessage dependency' do
517
+ clone = model.shallow_clone
518
+ expect(clone.id).to match(/fooId/)
519
+ expect(clone.connection_id).to eql(protocol_connection_id)
520
+ expect(as_since_epoch(clone.timestamp)).to eq(protocol_message_timestamp)
521
+ expect(clone.action).to eq(2)
522
+ end
523
+ end
524
+
525
+ context 'with embedded attributes for all fields' do
526
+ let(:message_timestamp) { as_since_epoch(Time.now) + 100 }
527
+ let(:connection_id) { random_str }
528
+ let(:model) { subject.new({ 'action' => 3, 'id' => 'fooId', 'connectionId' => connection_id, 'timestamp' => message_timestamp }) }
529
+
530
+ it 'creates a duplicate of the message without any ProtocolMessage dependency' do
531
+ clone = model.shallow_clone
532
+ expect(clone.id).to eql('fooId')
533
+ expect(clone.connection_id).to eql(connection_id)
534
+ expect(as_since_epoch(clone.timestamp)).to eq(message_timestamp)
535
+ expect(clone.action).to eq(3)
536
+ end
537
+ end
538
+
539
+ context 'with new attributes passed in to the method' do
540
+ let(:protocol_message) {
541
+ Ably::Models::ProtocolMessage.new('id' => 'fooId', 'connectionId' => protocol_connection_id, 'action' => 1, 'timestamp' => protocol_message_timestamp)
542
+ }
543
+ let(:protocol_connection_id) { random_str }
544
+ let(:model) { subject.new({ 'action' => 2 }, protocol_message: protocol_message) }
545
+
546
+ it 'creates a duplicate of the message without any ProtocolMessage dependency' do
547
+ clone = model.shallow_clone(id: 'newId', action: 1, timestamp: protocol_message_timestamp + 1000)
548
+ expect(clone.id).to match(/newId/)
549
+ expect(clone.connection_id).to eql(protocol_connection_id)
550
+ expect(as_since_epoch(clone.timestamp)).to eq(protocol_message_timestamp + 1000)
551
+ expect(clone.action).to eq(1)
552
+ end
553
+
554
+ context 'with an invalid ProtocolMessage (missing an ID)' do
555
+ let(:protocol_message) {
556
+ Ably::Models::ProtocolMessage.new('connectionId' => protocol_connection_id, 'action' => 1, 'timestamp' => protocol_message_timestamp)
557
+ }
558
+ it 'allows an ID to be passed in to the shallow clone that takes precedence' do
559
+ clone = model.shallow_clone(id: 'newId', action: 1, timestamp: protocol_message_timestamp + 1000)
560
+ expect(clone.id).to match(/newId/)
561
+ end
562
+ end
563
+
564
+ context 'with mixing of cases' do
565
+ it 'resolves case issues and can use camelCase or snake_case' do
566
+ clone = model.shallow_clone(connectionId: 'camelCaseSym')
567
+ expect(clone.connection_id).to match(/camelCaseSym/)
568
+
569
+ clone = model.shallow_clone('connectionId' => 'camelCaseStr')
570
+ expect(clone.connection_id).to match(/camelCaseStr/)
571
+
572
+ clone = model.shallow_clone(connection_id: 'snake_case_sym')
573
+ expect(clone.connection_id).to match(/snake_case_sym/)
574
+ end
575
+ end
576
+ end
577
+ end
386
578
  end
@@ -10,6 +10,7 @@ describe Ably::Models::ProtocolMessage do
10
10
  subject.new({ action: 1 }.merge(options))
11
11
  end
12
12
 
13
+ # TR4n, TR4b, TR4c, TR4d, TR4e
13
14
  it_behaves_like 'a model',
14
15
  with_simple_attributes: %w(id channel channel_serial connection_id connection_key),
15
16
  base_model_options: { action: 1 } do
@@ -134,7 +135,7 @@ describe Ably::Models::ProtocolMessage do
134
135
  end
135
136
  end
136
137
 
137
- context '#flags' do
138
+ context '#flags (#TR4i)' do
138
139
  context 'when nil' do
139
140
  let(:protocol_message) { new_protocol_message({}) }
140
141
 
@@ -151,12 +152,40 @@ describe Ably::Models::ProtocolMessage do
151
152
  end
152
153
  end
153
154
 
154
- context 'when has_presence' do
155
+ context 'when presence flag present' do
155
156
  let(:protocol_message) { new_protocol_message(flags: 1) }
156
157
 
157
158
  it '#has_presence_flag? is true' do
158
159
  expect(protocol_message.has_presence_flag?).to be_truthy
159
160
  end
161
+
162
+ it '#has_channel_resumed_flag? is false' do
163
+ expect(protocol_message.has_channel_resumed_flag?).to be_falsey
164
+ end
165
+ end
166
+
167
+ context 'when channel resumed flag present' do
168
+ let(:protocol_message) { new_protocol_message(flags: 4) }
169
+
170
+ it '#has_channel_resumed_flag? is true' do
171
+ expect(protocol_message.has_channel_resumed_flag?).to be_truthy
172
+ end
173
+
174
+ it '#has_presence_flag? is false' do
175
+ expect(protocol_message.has_presence_flag?).to be_falsey
176
+ end
177
+ end
178
+
179
+ context 'when channel resumed and presence flags present' do
180
+ let(:protocol_message) { new_protocol_message(flags: 5) }
181
+
182
+ it '#has_channel_resumed_flag? is true' do
183
+ expect(protocol_message.has_channel_resumed_flag?).to be_truthy
184
+ end
185
+
186
+ it '#has_presence_flag? is true' do
187
+ expect(protocol_message.has_presence_flag?).to be_truthy
188
+ end
160
189
  end
161
190
 
162
191
  context 'when has another future flag' do
@@ -165,6 +194,10 @@ describe Ably::Models::ProtocolMessage do
165
194
  it '#has_presence_flag? is false' do
166
195
  expect(protocol_message.has_presence_flag?).to be_falsey
167
196
  end
197
+
198
+ it '#has_backlog_flag? is true' do
199
+ expect(protocol_message.has_backlog_flag?).to be_truthy
200
+ end
168
201
  end
169
202
  end
170
203
 
@@ -265,7 +298,7 @@ describe Ably::Models::ProtocolMessage do
265
298
  end
266
299
  end
267
300
 
268
- context '#messages' do
301
+ context '#messages (#TR4k)' do
269
302
  let(:protocol_message) { new_protocol_message(messages: [{ name: 'test' }]) }
270
303
 
271
304
  it 'contains Message objects' do
@@ -275,7 +308,7 @@ describe Ably::Models::ProtocolMessage do
275
308
  end
276
309
  end
277
310
 
278
- context '#presence' do
311
+ context '#presence (#TR4l)' do
279
312
  let(:protocol_message) { new_protocol_message(presence: [{ action: 1, data: 'test' }]) }
280
313
 
281
314
  it 'contains PresenceMessage objects' do
@@ -285,7 +318,7 @@ describe Ably::Models::ProtocolMessage do
285
318
  end
286
319
  end
287
320
 
288
- context '#connection_details' do
321
+ context '#connection_details (#TR4o)' do
289
322
  let(:connection_details) { protocol_message.connection_details }
290
323
 
291
324
  context 'with a JSON value' do
@@ -312,7 +345,32 @@ describe Ably::Models::ProtocolMessage do
312
345
  end
313
346
  end
314
347
 
315
- context '#connection_key' do
348
+ context '#auth (#TR4p)' do
349
+ let(:auth) { protocol_message.auth }
350
+
351
+ context 'with a JSON value' do
352
+ let(:protocol_message) { new_protocol_message(auth: { accesstoken: 'foo' }) }
353
+
354
+ it 'contains a AuthDetails object' do
355
+ expect(auth).to be_a(Ably::Models::AuthDetails)
356
+ end
357
+
358
+ it 'contains the attributes from the JSON auth details' do
359
+ expect(auth.access_token).to eql('foo')
360
+ end
361
+ end
362
+
363
+ context 'without a JSON value' do
364
+ let(:protocol_message) { new_protocol_message({}) }
365
+
366
+ it 'contains an empty AuthDetails object' do
367
+ expect(auth).to be_a(Ably::Models::AuthDetails)
368
+ expect(auth.access_token).to eql(nil)
369
+ end
370
+ end
371
+ end
372
+
373
+ context '#connection_key (#TR4e)' do
316
374
  context 'existing only in #connection_details.connection_key' do
317
375
  let(:protocol_message) { new_protocol_message(connectionDetails: { connectionKey: 'key' }) }
318
376