ably 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -13
  2. data/README.md +11 -11
  3. data/SPEC.md +87 -87
  4. data/ably.gemspec +1 -1
  5. data/lib/ably/exceptions.rb +4 -1
  6. data/lib/ably/models/{paginated_resource.rb → paginated_result.rb} +8 -8
  7. data/lib/ably/models/presence_message.rb +1 -1
  8. data/lib/ably/modules/conversions.rb +15 -0
  9. data/lib/ably/modules/encodeable.rb +2 -2
  10. data/lib/ably/modules/event_emitter.rb +8 -8
  11. data/lib/ably/modules/safe_deferrable.rb +15 -3
  12. data/lib/ably/modules/state_emitter.rb +2 -2
  13. data/lib/ably/modules/state_machine.rb +1 -1
  14. data/lib/ably/realtime/channel.rb +2 -4
  15. data/lib/ably/realtime/channel/channel_manager.rb +1 -1
  16. data/lib/ably/realtime/client.rb +4 -4
  17. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +2 -2
  18. data/lib/ably/realtime/connection.rb +1 -1
  19. data/lib/ably/realtime/connection/connection_manager.rb +3 -3
  20. data/lib/ably/realtime/connection/connection_state_machine.rb +1 -1
  21. data/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  22. data/lib/ably/realtime/presence.rb +23 -5
  23. data/lib/ably/realtime/presence/members_map.rb +16 -13
  24. data/lib/ably/rest/channel.rb +3 -2
  25. data/lib/ably/rest/client.rb +2 -2
  26. data/lib/ably/rest/presence.rb +4 -4
  27. data/lib/ably/util/pub_sub.rb +1 -1
  28. data/lib/ably/util/safe_deferrable.rb +1 -1
  29. data/lib/ably/version.rb +1 -1
  30. data/spec/acceptance/realtime/channel_history_spec.rb +1 -1
  31. data/spec/acceptance/realtime/channel_spec.rb +5 -15
  32. data/spec/acceptance/realtime/connection_failures_spec.rb +2 -2
  33. data/spec/acceptance/realtime/connection_spec.rb +16 -14
  34. data/spec/acceptance/realtime/message_spec.rb +91 -8
  35. data/spec/acceptance/realtime/presence_spec.rb +190 -71
  36. data/spec/acceptance/realtime/stats_spec.rb +2 -2
  37. data/spec/acceptance/rest/channel_spec.rb +1 -1
  38. data/spec/acceptance/rest/encoders_spec.rb +1 -1
  39. data/spec/acceptance/rest/message_spec.rb +73 -1
  40. data/spec/acceptance/rest/presence_spec.rb +4 -2
  41. data/spec/unit/models/{paginated_resource_spec.rb → paginated_result_spec.rb} +18 -18
  42. data/spec/unit/modules/event_emitter_spec.rb +27 -27
  43. data/spec/unit/modules/state_emitter_spec.rb +1 -1
  44. data/spec/unit/realtime/channel_spec.rb +2 -2
  45. data/spec/unit/realtime/connection_spec.rb +3 -3
  46. data/spec/unit/realtime/presence_spec.rb +2 -2
  47. metadata +44 -44
@@ -37,6 +37,7 @@ module Ably
37
37
  # @return [Boolean] true if the message was published, otherwise false
38
38
  def publish(name, data)
39
39
  ensure_utf_8 :name, name
40
+ ensure_supported_payload data
40
41
 
41
42
  payload = {
42
43
  name: name,
@@ -60,7 +61,7 @@ module Ably
60
61
  # @option options [Symbol] :direction +:forwards+ or +:backwards+, defaults to +:backwards+
61
62
  # @option options [Integer] :limit Maximum number of messages to retrieve up to 1,000, defaults to 100
62
63
  #
63
- # @return [Ably::Models::PaginatedResource<Ably::Models::Message>] First {Ably::Models::PaginatedResource page} of {Ably::Models::Message} objects accessible with {Ably::Models::PaginatedResource#items #items}.
64
+ # @return [Ably::Models::PaginatedResult<Ably::Models::Message>] First {Ably::Models::PaginatedResult page} of {Ably::Models::Message} objects accessible with {Ably::Models::PaginatedResult#items #items}.
64
65
  #
65
66
  def history(options = {})
66
67
  url = "#{base_path}/messages"
@@ -78,7 +79,7 @@ module Ably
78
79
 
79
80
  response = client.get(url, options)
80
81
 
81
- Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |message|
82
+ Ably::Models::PaginatedResult.new(response, url, client, paginated_options) do |message|
82
83
  message.tap do |message|
83
84
  decode_message message
84
85
  end
@@ -150,7 +150,7 @@ module Ably
150
150
  # @option options [Integer] :limit Maximum number of messages to retrieve up to 1,000, defaults to 100
151
151
  # @option options [Symbol] :unit `:minute`, `:hour`, `:day` or `:month`. Defaults to `:minute`
152
152
  #
153
- # @return [Ably::Models::PaginatedResource<Ably::Models::Stats>] An Array of Stats
153
+ # @return [Ably::Models::PaginatedResult<Ably::Models::Stats>] An Array of Stats
154
154
  #
155
155
  def stats(options = {})
156
156
  options = {
@@ -168,7 +168,7 @@ module Ably
168
168
  url = '/stats'
169
169
  response = get(url, options)
170
170
 
171
- Ably::Models::PaginatedResource.new(response, url, self, paginated_options)
171
+ Ably::Models::PaginatedResult.new(response, url, self, paginated_options)
172
172
  end
173
173
 
174
174
  # Retrieve the Ably service time
@@ -25,7 +25,7 @@ module Ably
25
25
  # @param [Hash] options the options for the set of members present
26
26
  # @option options [Integer] :limit Maximum number of members to retrieve up to 1,000, defaults to 100
27
27
  #
28
- # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
28
+ # @return [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
29
29
  #
30
30
  def get(options = {})
31
31
  options = options = {
@@ -39,7 +39,7 @@ module Ably
39
39
 
40
40
  response = client.get(base_path, options)
41
41
 
42
- Ably::Models::PaginatedResource.new(response, base_path, client, paginated_options) do |presence_message|
42
+ Ably::Models::PaginatedResult.new(response, base_path, client, paginated_options) do |presence_message|
43
43
  presence_message.tap do |presence_message|
44
44
  decode_message presence_message
45
45
  end
@@ -54,7 +54,7 @@ module Ably
54
54
  # @option options [Symbol] :direction +:forwards+ or +:backwards+, defaults to +:backwards+
55
55
  # @option options [Integer] :limit Maximum number of messages to retrieve up to 1,000, defaults to 100
56
56
  #
57
- # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
57
+ # @return [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
58
58
  #
59
59
  def history(options = {})
60
60
  url = "#{base_path}/history"
@@ -72,7 +72,7 @@ module Ably
72
72
 
73
73
  response = client.get(url, options)
74
74
 
75
- Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |presence_message|
75
+ Ably::Models::PaginatedResult.new(response, url, client, paginated_options) do |presence_message|
76
76
  presence_message.tap do |presence_message|
77
77
  decode_message presence_message
78
78
  end
@@ -37,7 +37,7 @@ module Ably::Util
37
37
  configure_event_emitter options
38
38
 
39
39
  alias_method :subscribe, :unsafe_on
40
- alias_method :publish, :trigger
40
+ alias_method :publish, :emit
41
41
  alias_method :unsubscribe, :off
42
42
  end
43
43
  end
@@ -1,6 +1,6 @@
1
1
  module Ably::Util
2
2
  # SafeDeferrable class provides a Deferrable that is safe to use for for public interfaces
3
- # of this client library. Any exceptions raised in the success or failure callbacks is
3
+ # of this client library. Any exceptions raised in the success or failure callbacks are
4
4
  # caught and logged to the provided logger.
5
5
  #
6
6
  # An exception in a callback provided by a developer should not break this client library
data/lib/ably/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ably
2
- VERSION = '0.8.1'
2
+ VERSION = '0.8.2'
3
3
  end
@@ -24,7 +24,7 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
24
24
  expect(history).to be_a(Ably::Util::SafeDeferrable)
25
25
  history.callback do |page|
26
26
  expect(page.items.count).to eql(1)
27
- expect(page).to be_a(Ably::Models::PaginatedResource)
27
+ expect(page).to be_a(Ably::Models::PaginatedResult)
28
28
  stop_reactor
29
29
  end
30
30
  end
@@ -13,9 +13,9 @@ describe Ably::Realtime::Channel, :event_machine do
13
13
  let(:messages) { [] }
14
14
 
15
15
  describe 'initialization' do
16
- context 'with :connect_automatically option set to false on connection' do
16
+ context 'with :auto_connect option set to false on connection' do
17
17
  let(:client) do
18
- Ably::Realtime::Client.new(default_options.merge(connect_automatically: false))
18
+ Ably::Realtime::Client.new(default_options.merge(auto_connect: false))
19
19
  end
20
20
 
21
21
  it 'remains initialized when accessing a channel' do
@@ -32,16 +32,6 @@ describe Ably::Realtime::Channel, :event_machine do
32
32
  stop_reactor
33
33
  end
34
34
  end
35
-
36
- it 'opens a connection implicitly when accessing #presence' do
37
- client.channel('test').tap do |channel|
38
- channel.on(:attached) do
39
- expect(client.connection).to be_connected
40
- stop_reactor
41
- end
42
- channel.presence
43
- end
44
- end
45
35
  end
46
36
  end
47
37
 
@@ -162,7 +152,7 @@ describe Ably::Realtime::Channel, :event_machine do
162
152
  end
163
153
  let(:restricted_channel) { restricted_client.channel("cannot_subscribe") }
164
154
 
165
- it 'triggers failed event' do
155
+ it 'emits failed event' do
166
156
  restricted_channel.attach
167
157
  restricted_channel.on(:failed) do |error|
168
158
  expect(restricted_channel.state).to eq(:failed)
@@ -179,7 +169,7 @@ describe Ably::Realtime::Channel, :event_machine do
179
169
  end
180
170
  end
181
171
 
182
- it 'triggers an error event' do
172
+ it 'emits an error event' do
183
173
  restricted_channel.attach
184
174
  restricted_channel.on(:error) do |error|
185
175
  expect(restricted_channel.state).to eq(:failed)
@@ -458,7 +448,7 @@ describe Ably::Realtime::Channel, :event_machine do
458
448
  end
459
449
  end
460
450
 
461
- it 'triggers an error event on the channel' do
451
+ it 'emits an error event on the channel' do
462
452
  channel.attach do
463
453
  channel.on(:error) do |error|
464
454
  expect(error).to eql(connection_error)
@@ -319,7 +319,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
319
319
  disconnected: { retry_every: retry_every, max_time_in_state: 60 })
320
320
  end
321
321
 
322
- it "retries every CONNECT_RETRY_CONFIG[:disconnected][:retry_every] seconds" do
322
+ it "retries every #{Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG[:disconnected][:retry_every]} seconds" do
323
323
  fail_if_suspended_or_failed
324
324
 
325
325
  stubbed_first_attempt = false
@@ -415,7 +415,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
415
415
  end
416
416
  end
417
417
 
418
- it 'triggers the resume callback', api_private: true do
418
+ it 'executes the resume callback', api_private: true do
419
419
  channel.attach do
420
420
  connection.transport.close_connection_after_writing
421
421
  connection.on_resume do
@@ -27,9 +27,9 @@ describe Ably::Realtime::Connection, :event_machine do
27
27
  end
28
28
  end
29
29
 
30
- context 'with :connect_automatically option set to false' do
30
+ context 'with :auto_connect option set to false' do
31
31
  let(:client) do
32
- Ably::Realtime::Client.new(default_options.merge(connect_automatically: false))
32
+ Ably::Realtime::Client.new(default_options.merge(auto_connect: false))
33
33
  end
34
34
 
35
35
  it 'does not connect automatically' do
@@ -201,10 +201,10 @@ describe Ably::Realtime::Connection, :event_machine do
201
201
 
202
202
  context 'initialization state changes' do
203
203
  let(:phases) { [:connecting, :connected] }
204
- let(:events_triggered) { [] }
204
+ let(:events_emitted) { [] }
205
205
  let(:test_expectation) do
206
206
  Proc.new do
207
- expect(events_triggered).to eq(phases)
207
+ expect(events_emitted).to eq(phases)
208
208
  stop_reactor
209
209
  end
210
210
  end
@@ -212,20 +212,20 @@ describe Ably::Realtime::Connection, :event_machine do
212
212
  def expect_ordered_phases
213
213
  phases.each do |phase|
214
214
  connection.on(phase) do
215
- events_triggered << phase
216
- test_expectation.call if events_triggered.length == phases.length
215
+ events_emitted << phase
216
+ test_expectation.call if events_emitted.length == phases.length
217
217
  end
218
218
  end
219
219
  end
220
220
 
221
221
  context 'with implicit #connect' do
222
- it 'are triggered in order' do
222
+ it 'are emitted in order' do
223
223
  expect_ordered_phases
224
224
  end
225
225
  end
226
226
 
227
227
  context 'with explicit #connect' do
228
- it 'are triggered in order' do
228
+ it 'are emitted in order' do
229
229
  expect_ordered_phases
230
230
  connection.connect
231
231
  end
@@ -542,10 +542,10 @@ describe Ably::Realtime::Connection, :event_machine do
542
542
  let(:states) { Hash.new }
543
543
  let(:client_options) { default_options.merge(log_level: :none) }
544
544
 
545
- it 'is composed of connection id and serial that is kept up to date with each message ACK received' do
545
+ it 'is composed of connection key and serial that is kept up to date with each message ACK received' do
546
546
  connection.on(:connected) do
547
547
  expected_serial = -1
548
- expect(connection.id).to_not be_nil
548
+ expect(connection.key).to_not be_nil
549
549
  expect(connection.serial).to eql(expected_serial)
550
550
 
551
551
  client.channel('test').attach do |channel|
@@ -556,6 +556,8 @@ describe Ably::Realtime::Connection, :event_machine do
556
556
  channel.publish('event', 'data') do
557
557
  expected_serial += 1 # attach message received
558
558
  expect(connection.serial).to eql(expected_serial)
559
+
560
+ expect(connection.recovery_key).to eql("#{connection.key}:#{connection.serial}")
559
561
  stop_reactor
560
562
  end
561
563
  end
@@ -621,7 +623,7 @@ describe Ably::Realtime::Connection, :event_machine do
621
623
  end
622
624
  end
623
625
 
624
- it 'does not trigger a resume callback', api_private: true do
626
+ it 'does not call a resume callback', api_private: true do
625
627
  connection.once(:connected) do
626
628
  connection.transition_state_machine! :failed
627
629
  end
@@ -629,7 +631,7 @@ describe Ably::Realtime::Connection, :event_machine do
629
631
  connection.once(:failed) do
630
632
  recover_client = Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
631
633
  recover_client.connection.on_resume do
632
- raise 'Should not trigger resume callback'
634
+ raise 'Should not call the resume callback'
633
635
  end
634
636
  recover_client.connection.on(:connected) do
635
637
  EventMachine.add_timer(0.5) { stop_reactor }
@@ -676,7 +678,7 @@ describe Ably::Realtime::Connection, :event_machine do
676
678
  context 'with invalid formatted value sent to server' do
677
679
  let(:client_options) { default_options.merge(recover: 'not-a-valid-connection-key:1', log_level: :none) }
678
680
 
679
- it 'triggers a fatal error on the connection object, sets the #error_reason and disconnects' do
681
+ it 'emits a fatal error on the connection object, sets the #error_reason and disconnects' do
680
682
  connection.once(:error) do |error|
681
683
  expect(connection.state).to eq(:failed)
682
684
  expect(error.message).to match(/Invalid connection key/)
@@ -691,7 +693,7 @@ describe Ably::Realtime::Connection, :event_machine do
691
693
  context 'with expired (missing) value sent to server' do
692
694
  let(:client_options) { default_options.merge(recover: '0123456789abcdef:0', log_level: :fatal) }
693
695
 
694
- it 'triggers an error on the connection object, sets the #error_reason, yet will connect anyway' do
696
+ it 'emits an error on the connection object, sets the #error_reason, yet will connect anyway' do
695
697
  connection.once(:error) do |error|
696
698
  expect(connection.state).to eq(:connected)
697
699
  expect(error.message).to match(/Invalid connection key/i)
@@ -32,6 +32,87 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
32
32
  end
33
33
  end
34
34
 
35
+ context 'with supported data payload content type' do
36
+ def publish_and_check_data(data)
37
+ channel.attach
38
+ channel.publish 'event', data
39
+ channel.subscribe do |message|
40
+ expect(message.data).to eql(data)
41
+ stop_reactor
42
+ end
43
+ end
44
+
45
+ context 'JSON Object (Hash)' do
46
+ let(:data) { { 'Hash' => 'true' } }
47
+
48
+ it 'is encoded and decoded to the same hash' do
49
+ publish_and_check_data data
50
+ end
51
+ end
52
+
53
+ context 'JSON Array' do
54
+ let(:data) { [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] }
55
+
56
+ it 'is encoded and decoded to the same Array' do
57
+ publish_and_check_data data
58
+ end
59
+ end
60
+
61
+ context 'String' do
62
+ let(:data) { random_str }
63
+
64
+ it 'is encoded and decoded to the same Array' do
65
+ publish_and_check_data data
66
+ end
67
+ end
68
+
69
+ context 'Binary' do
70
+ let(:data) { Base64.encode64(random_str) }
71
+
72
+ it 'is encoded and decoded to the same Array' do
73
+ publish_and_check_data data
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'with unsupported data payload content type' do
79
+ context 'Integer' do
80
+ let(:data) { 1 }
81
+
82
+ it 'is raises an UnsupportedDataTypeError 40011 exception' do
83
+ expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
84
+ stop_reactor
85
+ end
86
+ end
87
+
88
+ context 'Float' do
89
+ let(:data) { 1.1 }
90
+
91
+ it 'is raises an UnsupportedDataTypeError 40011 exception' do
92
+ expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
93
+ stop_reactor
94
+ end
95
+ end
96
+
97
+ context 'Boolean' do
98
+ let(:data) { true }
99
+
100
+ it 'is raises an UnsupportedDataTypeError 40011 exception' do
101
+ expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
102
+ stop_reactor
103
+ end
104
+ end
105
+
106
+ context 'False' do
107
+ let(:data) { false }
108
+
109
+ it 'is raises an UnsupportedDataTypeError 40011 exception' do
110
+ expect { channel.publish 'event', data }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
111
+ stop_reactor
112
+ end
113
+ end
114
+ end
115
+
35
116
  context 'with ASCII_8BIT message name' do
36
117
  let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) }
37
118
  it 'is converted into UTF_8' do
@@ -410,7 +491,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
410
491
  end
411
492
  end
412
493
 
413
- it 'triggers a Cipher error on the channel' do
494
+ it 'emits a Cipher error on the channel' do
414
495
  unencrypted_channel_client2.attach do
415
496
  encrypted_channel_client1.publish 'example', payload
416
497
  unencrypted_channel_client2.on(:error) do |error|
@@ -441,7 +522,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
441
522
  end
442
523
  end
443
524
 
444
- it 'triggers a Cipher error on the channel' do
525
+ it 'emits a Cipher error on the channel' do
445
526
  encrypted_channel_client2.attach do
446
527
  encrypted_channel_client1.publish 'example', payload
447
528
  encrypted_channel_client2.on(:error) do |error|
@@ -464,15 +545,17 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
464
545
  let(:payload) { MessagePack.pack({ 'key' => random_str }) }
465
546
 
466
547
  it 'delivers the message but still encrypted with the cipher details in the #encoding attribute' do
467
- encrypted_channel_client1.publish 'example', payload
468
- encrypted_channel_client2.subscribe do |message|
469
- expect(message.data).to_not eql(payload)
470
- expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
471
- stop_reactor
548
+ encrypted_channel_client2.attach do
549
+ encrypted_channel_client1.publish 'example', payload
550
+ encrypted_channel_client2.subscribe do |message|
551
+ expect(message.data).to_not eql(payload)
552
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
553
+ stop_reactor
554
+ end
472
555
  end
473
556
  end
474
557
 
475
- it 'triggers a Cipher error on the channel' do
558
+ it 'emits a Cipher error on the channel' do
476
559
  encrypted_channel_client2.attach do
477
560
  encrypted_channel_client1.publish 'example', payload
478
561
  encrypted_channel_client2.on(:error) do |error|
@@ -53,6 +53,107 @@ describe Ably::Realtime::Presence, :event_machine do
53
53
  end
54
54
  end
55
55
 
56
+ context 'with supported data payload content type' do
57
+ def register_presence_and_check_data(method_name, data)
58
+ if method_name.to_s.match(/_client/)
59
+ presence_client_one.public_send(method_name, 'client_id', data: data)
60
+ else
61
+ presence_client_one.public_send(method_name, data: data)
62
+ end
63
+
64
+ presence_client_one.subscribe do |presence_message|
65
+ expect(presence_message.data).to eql(data)
66
+ stop_reactor
67
+ end
68
+ end
69
+
70
+ context 'JSON Object (Hash)' do
71
+ let(:data) { { 'Hash' => 'true' } }
72
+
73
+ it 'is encoded and decoded to the same hash' do
74
+ setup_test(method_name, args, options) do
75
+ register_presence_and_check_data method_name, data
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'JSON Array' do
81
+ let(:data) { [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] }
82
+
83
+ it 'is encoded and decoded to the same Array' do
84
+ setup_test(method_name, args, options) do
85
+ register_presence_and_check_data method_name, data
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'String' do
91
+ let(:data) { random_str }
92
+
93
+ it 'is encoded and decoded to the same Array' do
94
+ setup_test(method_name, args, options) do
95
+ register_presence_and_check_data method_name, data
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'Binary' do
101
+ let(:data) { Base64.encode64(random_str) }
102
+
103
+ it 'is encoded and decoded to the same Array' do
104
+ setup_test(method_name, args, options) do
105
+ register_presence_and_check_data method_name, data
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'with unsupported data payload content type' do
112
+ def presence_action(method_name, data)
113
+ if method_name.to_s.match(/_client/)
114
+ presence_client_one.public_send(method_name, 'client_id', data: data)
115
+ else
116
+ presence_client_one.public_send(method_name, data: data)
117
+ end
118
+ end
119
+
120
+ context 'Integer' do
121
+ let(:data) { 1 }
122
+
123
+ it 'raises an UnsupportedDataTypeError 40011 exception' do
124
+ expect { presence_action(method_name, data) }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
125
+ stop_reactor
126
+ end
127
+ end
128
+
129
+ context 'Float' do
130
+ let(:data) { 1.1 }
131
+
132
+ it 'raises an UnsupportedDataTypeError 40011 exception' do
133
+ expect { presence_action(method_name, data) }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
134
+ stop_reactor
135
+ end
136
+ end
137
+
138
+ context 'Boolean' do
139
+ let(:data) { true }
140
+
141
+ it 'raises an UnsupportedDataTypeError 40011 exception' do
142
+ expect { presence_action(method_name, data) }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
143
+ stop_reactor
144
+ end
145
+ end
146
+
147
+ context 'False' do
148
+ let(:data) { false }
149
+
150
+ it 'raises an UnsupportedDataTypeError 40011 exception' do
151
+ expect { presence_action(method_name, data) }.to raise_error(Ably::Exceptions::UnsupportedDataTypeError)
152
+ stop_reactor
153
+ end
154
+ end
155
+ end
156
+
56
157
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
57
158
  setup_test(method_name, args, options) do
58
159
  expect(presence_client_one.public_send(method_name, args)).to be_a(Ably::Util::SafeDeferrable)
@@ -153,13 +254,15 @@ describe Ably::Realtime::Presence, :event_machine do
153
254
  stop_reactor
154
255
  end
155
256
 
156
- it 'will trigger an :in_sync event when synchronisation is complete' do
257
+ it 'will emit an :in_sync event when synchronisation is complete' do
157
258
  presence_client_one.enter
158
259
  presence_client_two.enter
159
260
 
160
261
  presence_anonymous_client.members.once(:in_sync) do
161
262
  stop_reactor
162
263
  end
264
+
265
+ channel_anonymous_client.attach
163
266
  end
164
267
 
165
268
  context 'before server sync complete' do
@@ -180,6 +283,8 @@ describe Ably::Realtime::Presence, :event_machine do
180
283
  expect(member_ids.uniq.count).to eql(2)
181
284
  stop_reactor
182
285
  end
286
+
287
+ channel_anonymous_client.attach
183
288
  end
184
289
  end
185
290
  end
@@ -200,7 +305,7 @@ describe Ably::Realtime::Presence, :event_machine do
200
305
  presence_client_one.enter do
201
306
  channel_anonymous_client.attach do
202
307
  expect(channel_anonymous_client.presence).to_not be_sync_complete
203
- channel_anonymous_client.presence.get do
308
+ channel_anonymous_client.presence.get(wait_for_sync: true) do
204
309
  expect(channel_anonymous_client.presence).to be_sync_complete
205
310
  stop_reactor
206
311
  end
@@ -319,7 +424,7 @@ describe Ably::Realtime::Presence, :event_machine do
319
424
  end
320
425
  end
321
426
 
322
- 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' do
427
+ 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 with :wait_for_sync' do
323
428
  left_client = 10
324
429
  left_client_id = "client:#{left_client}"
325
430
 
@@ -341,7 +446,7 @@ describe Ably::Realtime::Presence, :event_machine do
341
446
  member_left_emitted = true
342
447
  end
343
448
 
344
- presence_anonymous_client.get do |members|
449
+ presence_anonymous_client.get(wait_for_sync: true) do |members|
345
450
  expect(members.count).to eql(enter_expected_count - 1)
346
451
  expect(member_left_emitted).to eql(true)
347
452
  expect(members.map(&:client_id)).to_not include(left_client_id)
@@ -365,43 +470,40 @@ describe Ably::Realtime::Presence, :event_machine do
365
470
  end
366
471
 
367
472
  context '#get' do
368
- it 'waits until sync is complete', event_machine: 15 do
369
- enter_expected_count.times do |index|
370
- presence_client_one.enter_client("client:#{index}") do |message|
371
- entered << message
372
- next unless entered.count == enter_expected_count
373
-
374
- presence_anonymous_client.get do |members|
375
- expect(members.map(&:client_id).uniq.count).to eql(enter_expected_count)
376
- expect(members.count).to eql(enter_expected_count)
377
- stop_reactor
473
+ context 'with :wait_for_sync option set to true' do
474
+ it 'waits until sync is complete', event_machine: 15 do
475
+ enter_expected_count.times do |index|
476
+ presence_client_one.enter_client("client:#{index}") do |message|
477
+ entered << message
478
+ next unless entered.count == enter_expected_count
479
+
480
+ presence_anonymous_client.get(wait_for_sync: true) do |members|
481
+ expect(members.map(&:client_id).uniq.count).to eql(enter_expected_count)
482
+ expect(members.count).to eql(enter_expected_count)
483
+ stop_reactor
484
+ end
378
485
  end
379
486
  end
380
487
  end
381
488
  end
382
- end
383
- end
384
- end
385
- end
386
489
 
387
- context 'automatic attachment of channel on access to presence object' do
388
- it 'is implicit if presence state is initialized' do
389
- channel_client_one.presence
390
- channel_client_one.on(:attached) do
391
- expect(channel_client_one.state).to eq(:attached)
392
- stop_reactor
393
- end
394
- end
395
-
396
- it 'is disabled if presence state is not initialized' do
397
- channel_client_one.attach do
398
- channel_client_one.detach do
399
- expect(channel_client_one.state).to eq(:detached)
400
-
401
- channel_client_one.presence # access the presence object
402
- EventMachine.add_timer(1) do
403
- expect(channel_client_one.state).to eq(:detached)
404
- stop_reactor
490
+ context 'by default' do
491
+ it 'it does not wait for sync', event_machine: 15 do
492
+ enter_expected_count.times do |index|
493
+ presence_client_one.enter_client("client:#{index}") do |message|
494
+ entered << message
495
+ next unless entered.count == enter_expected_count
496
+
497
+ channel_anonymous_client.attach do
498
+ presence_anonymous_client.get do |members|
499
+ expect(presence_anonymous_client.members).to_not be_in_sync
500
+ expect(members.count).to eql(0)
501
+ stop_reactor
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
405
507
  end
406
508
  end
407
509
  end
@@ -571,7 +673,7 @@ describe Ably::Realtime::Presence, :event_machine do
571
673
  end
572
674
 
573
675
  context 'when set to nil' do
574
- it 'emits the previously defined value as a convenience' do
676
+ it 'emits a nil value for the data attribute when leaving' do
575
677
  presence_client_one.enter data: enter_data do
576
678
  presence_client_one.leave data: nil
577
679
  end
@@ -595,6 +697,22 @@ describe Ably::Realtime::Presence, :event_machine do
595
697
  end
596
698
  end
597
699
  end
700
+
701
+ context 'and sync is complete' do
702
+ it 'does not cache members that have left' do
703
+ presence_client_one.enter data: enter_data do
704
+ expect(presence_client_one.members).to be_in_sync
705
+ expect(presence_client_one.members.send(:members).count).to eql(1)
706
+ presence_client_one.leave data: data
707
+ end
708
+
709
+ presence_client_one.subscribe(:leave) do |presence_message|
710
+ expect(presence_message.data).to eql(data)
711
+ expect(presence_client_one.members.send(:members).count).to eql(0)
712
+ stop_reactor
713
+ end
714
+ end
715
+ end
598
716
  end
599
717
 
600
718
  it 'raises an exception if not entered' do
@@ -911,43 +1029,45 @@ describe Ably::Realtime::Presence, :event_machine do
911
1029
  )
912
1030
  end
913
1031
 
914
- it 'fails if the connection fails' do
915
- when_all(*connect_members_deferrables) do
916
- channel_client_two.attach do
917
- client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
918
- if protocol_message.action == :sync
919
- sync_pages_received << protocol_message
920
- force_connection_failure client_two if sync_pages_received.count == 1
1032
+ context 'when :wait_for_sync is true' do
1033
+ it 'fails if the connection fails' do
1034
+ when_all(*connect_members_deferrables) do
1035
+ channel_client_two.attach do
1036
+ client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
1037
+ if protocol_message.action == :sync
1038
+ sync_pages_received << protocol_message
1039
+ force_connection_failure client_two if sync_pages_received.count == 1
1040
+ end
921
1041
  end
922
1042
  end
923
- end
924
1043
 
925
- presence_client_two.get.tap do |deferrable|
926
- deferrable.callback { raise 'Get should not succeed' }
927
- deferrable.errback do |error|
928
- stop_reactor
1044
+ presence_client_two.get(wait_for_sync: true).tap do |deferrable|
1045
+ deferrable.callback { raise 'Get should not succeed' }
1046
+ deferrable.errback do |error|
1047
+ stop_reactor
1048
+ end
929
1049
  end
930
1050
  end
931
1051
  end
932
- end
933
1052
 
934
- it 'fails if the channel is detached' do
935
- when_all(*connect_members_deferrables) do
936
- channel_client_two.attach do
937
- client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
938
- if protocol_message.action == :sync
939
- # prevent any more SYNC messages coming through
940
- client_two.connection.transport.__incoming_protocol_msgbus__.unsubscribe
941
- channel_client_two.change_state :detaching
942
- channel_client_two.change_state :detached
1053
+ it 'fails if the channel is detached' do
1054
+ when_all(*connect_members_deferrables) do
1055
+ channel_client_two.attach do
1056
+ client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
1057
+ if protocol_message.action == :sync
1058
+ # prevent any more SYNC messages coming through
1059
+ client_two.connection.transport.__incoming_protocol_msgbus__.unsubscribe
1060
+ channel_client_two.change_state :detaching
1061
+ channel_client_two.change_state :detached
1062
+ end
943
1063
  end
944
1064
  end
945
- end
946
1065
 
947
- presence_client_two.get.tap do |deferrable|
948
- deferrable.callback { raise 'Get should not succeed' }
949
- deferrable.errback do |error|
950
- stop_reactor
1066
+ presence_client_two.get(wait_for_sync: true).tap do |deferrable|
1067
+ deferrable.callback { raise 'Get should not succeed' }
1068
+ deferrable.errback do |error|
1069
+ stop_reactor
1070
+ end
951
1071
  end
952
1072
  end
953
1073
  end
@@ -1059,12 +1179,12 @@ describe Ably::Realtime::Presence, :event_machine do
1059
1179
  end
1060
1180
 
1061
1181
  wait_until(proc { clients_entered[:client_one] + clients_entered[:client_two] == total_members * 2 }) do
1062
- presence_anonymous_client.get do |anonymous_members|
1182
+ presence_anonymous_client.get(wait_for_sync: true) do |anonymous_members|
1063
1183
  expect(anonymous_members.count).to eq(total_members)
1064
1184
  expect(anonymous_members.map(&:client_id).uniq.count).to eq(total_members)
1065
1185
 
1066
- presence_client_one.get do |client_one_members|
1067
- presence_client_two.get do |client_two_members|
1186
+ presence_client_one.get(wait_for_sync: true) do |client_one_members|
1187
+ presence_client_two.get(wait_for_sync: true) do |client_two_members|
1068
1188
  expect(client_one_members.count).to eq(total_members)
1069
1189
  expect(client_one_members.count).to eq(client_two_members.count)
1070
1190
  stop_reactor
@@ -1319,7 +1439,7 @@ describe Ably::Realtime::Presence, :event_machine do
1319
1439
 
1320
1440
  specify 'expect :left event with client data from enter event' do
1321
1441
  presence_client_one.subscribe(:leave) do |message|
1322
- presence_client_one.get do |members|
1442
+ presence_client_one.get(wait_for_sync: true) do |members|
1323
1443
  expect(members.count).to eq(0)
1324
1444
  expect(message.data).to eql(data_payload)
1325
1445
  stop_reactor
@@ -1335,8 +1455,7 @@ describe Ably::Realtime::Presence, :event_machine do
1335
1455
  let(:members_count) { 400 }
1336
1456
  let(:sync_pages_received) { [] }
1337
1457
 
1338
- # Will re-enable once https://github.com/ably/realtime/issues/91 is resolved
1339
- skip 'resumes the SYNC operation', em_timeout: 15 do
1458
+ it 'resumes the SYNC operation', em_timeout: 15 do
1340
1459
  when_all(*members_count.times.map do |index|
1341
1460
  presence_client_one.enter_client("client:#{index}")
1342
1461
  end) do
@@ -1349,7 +1468,7 @@ describe Ably::Realtime::Presence, :event_machine do
1349
1468
  end
1350
1469
  end
1351
1470
 
1352
- presence_client_two.get do |members|
1471
+ presence_client_two.get(wait_for_sync: true) do |members|
1353
1472
  expect(members.count).to eql(members_count)
1354
1473
  expect(members.map(&:member_key).uniq.count).to eql(members_count)
1355
1474
  stop_reactor