ably 1.0.7 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.travis.yml +4 -4
  4. data/CHANGELOG.md +26 -3
  5. data/Rakefile +32 -0
  6. data/SPEC.md +920 -565
  7. data/ably.gemspec +9 -4
  8. data/lib/ably/auth.rb +28 -2
  9. data/lib/ably/exceptions.rb +8 -2
  10. data/lib/ably/models/channel_state_change.rb +1 -1
  11. data/lib/ably/models/connection_state_change.rb +1 -1
  12. data/lib/ably/models/device_details.rb +87 -0
  13. data/lib/ably/models/device_push_details.rb +86 -0
  14. data/lib/ably/models/error_info.rb +23 -2
  15. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  16. data/lib/ably/models/protocol_message.rb +32 -2
  17. data/lib/ably/models/push_channel_subscription.rb +89 -0
  18. data/lib/ably/modules/conversions.rb +1 -1
  19. data/lib/ably/modules/encodeable.rb +1 -1
  20. data/lib/ably/modules/exception_codes.rb +128 -0
  21. data/lib/ably/modules/model_common.rb +15 -2
  22. data/lib/ably/modules/state_machine.rb +1 -1
  23. data/lib/ably/realtime.rb +1 -0
  24. data/lib/ably/realtime/auth.rb +1 -1
  25. data/lib/ably/realtime/channel.rb +24 -102
  26. data/lib/ably/realtime/channel/channel_manager.rb +2 -6
  27. data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  28. data/lib/ably/realtime/channel/publisher.rb +74 -0
  29. data/lib/ably/realtime/channel/push_channel.rb +62 -0
  30. data/lib/ably/realtime/client.rb +87 -0
  31. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  32. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  33. data/lib/ably/realtime/connection.rb +8 -5
  34. data/lib/ably/realtime/connection/connection_manager.rb +7 -7
  35. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  36. data/lib/ably/realtime/presence.rb +4 -4
  37. data/lib/ably/realtime/presence/members_map.rb +3 -3
  38. data/lib/ably/realtime/push.rb +40 -0
  39. data/lib/ably/realtime/push/admin.rb +61 -0
  40. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  41. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  42. data/lib/ably/rest.rb +1 -0
  43. data/lib/ably/rest/channel.rb +33 -5
  44. data/lib/ably/rest/channel/push_channel.rb +62 -0
  45. data/lib/ably/rest/client.rb +137 -28
  46. data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  47. data/lib/ably/rest/presence.rb +1 -0
  48. data/lib/ably/rest/push.rb +42 -0
  49. data/lib/ably/rest/push/admin.rb +54 -0
  50. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  51. data/lib/ably/rest/push/device_registrations.rb +103 -0
  52. data/lib/ably/version.rb +7 -2
  53. data/spec/acceptance/realtime/auth_spec.rb +6 -8
  54. data/spec/acceptance/realtime/channel_spec.rb +166 -51
  55. data/spec/acceptance/realtime/client_spec.rb +149 -0
  56. data/spec/acceptance/realtime/connection_failures_spec.rb +1 -1
  57. data/spec/acceptance/realtime/connection_spec.rb +4 -4
  58. data/spec/acceptance/realtime/message_spec.rb +19 -17
  59. data/spec/acceptance/realtime/presence_spec.rb +5 -5
  60. data/spec/acceptance/realtime/push_admin_spec.rb +696 -0
  61. data/spec/acceptance/realtime/push_spec.rb +27 -0
  62. data/spec/acceptance/rest/auth_spec.rb +4 -3
  63. data/spec/acceptance/rest/base_spec.rb +2 -2
  64. data/spec/acceptance/rest/client_spec.rb +129 -10
  65. data/spec/acceptance/rest/message_spec.rb +175 -4
  66. data/spec/acceptance/rest/push_admin_spec.rb +896 -0
  67. data/spec/acceptance/rest/push_spec.rb +25 -0
  68. data/spec/acceptance/rest/time_spec.rb +1 -1
  69. data/spec/run_parallel_tests +33 -0
  70. data/spec/unit/logger_spec.rb +10 -3
  71. data/spec/unit/models/device_details_spec.rb +102 -0
  72. data/spec/unit/models/device_push_details_spec.rb +101 -0
  73. data/spec/unit/models/error_info_spec.rb +51 -3
  74. data/spec/unit/models/message_spec.rb +17 -2
  75. data/spec/unit/models/presence_message_spec.rb +1 -1
  76. data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  77. data/spec/unit/realtime/client_spec.rb +12 -0
  78. data/spec/unit/realtime/push_channel_spec.rb +36 -0
  79. data/spec/unit/rest/channel_spec.rb +8 -1
  80. data/spec/unit/rest/client_spec.rb +30 -0
  81. data/spec/unit/rest/push_channel_spec.rb +36 -0
  82. metadata +71 -8
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest::Push do
5
+ vary_by_protocol do
6
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
7
+ let(:client_options) { default_options }
8
+ let(:client) do
9
+ Ably::Rest::Client.new(client_options)
10
+ end
11
+ subject { client.push }
12
+
13
+ describe '#activate' do
14
+ it 'raises an unsupported exception' do
15
+ expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
16
+ end
17
+ end
18
+
19
+ describe '#deactivate' do
20
+ it 'raises an unsupported exception' do
21
+ expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -13,7 +13,7 @@ describe Ably::Rest::Client, '#time' do
13
13
 
14
14
  context 'with reconfigured HTTP timeout' do
15
15
  let(:client) do
16
- Ably::Rest::Client.new(http_request_timeout: 0.0001, key: api_key, environment: environment, protocol: protocol)
16
+ Ably::Rest::Client.new(http_request_timeout: 0.0001, key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true)
17
17
  end
18
18
 
19
19
  it 'should raise a timeout exception' do
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ #
3
+ # Run the unit tests first without RSpec parallel, then run acceptance tests in parallel
4
+ #
5
+ # When splitting all tests across all parallel processes, it's quite plausible
6
+ # that some processes only run a majority of unit tests, whilst others only run a
7
+ # a majority of acceptance tests. This ensures acceptance tests are split out.
8
+
9
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
10
+
11
+ bundle exec rspec "${DIR}/unit"
12
+ unit_status=$?
13
+
14
+ if ruby -v | grep -e "1.9"; then
15
+ # Output with test ID is not supported with this old version of RSpec
16
+ # So it will be jumbled sadly for 1.9.*
17
+ bundle exec parallel_rspec "${DIR}/acceptance"
18
+ else
19
+ bundle exec parallel_rspec "${DIR}/acceptance" --prefix-output-with-test-env-number
20
+ fi
21
+ acceptance_status=$?
22
+
23
+ if [ $unit_status -ne 0 ]; then
24
+ echo -e "\e[31m⚠ Note: Unit tests have also failed, but are not listed in the test failures above. Scroll up to the unit tests ⚠\e[0m"
25
+ fi
26
+
27
+ if [ $unit_status -ne 0 ] || [ $acceptance_status -ne 0 ]; then
28
+ echo "Unit tests exit code: ${unit_status}"
29
+ echo "Acceptance tests exit code: ${acceptance_status}"
30
+ exit 1
31
+ fi
32
+
33
+
@@ -17,11 +17,15 @@ describe Ably::Logger do
17
17
  end
18
18
 
19
19
  context 'internals', :api_private do
20
- it 'delegates to the logger object' do
21
- expect(subject.logger).to receive(:warn) do |*args, &block|
20
+ it 'delegates to the default Logger object' do
21
+ received = false
22
+ expect(subject.logger).to be_a(::Logger)
23
+ allow_any_instance_of(::Logger).to receive(:warn) do |*args, &block|
22
24
  expect(args.concat([block ? block.call : nil]).join(',')).to match(/message/)
25
+ received = true
23
26
  end
24
27
  subject.warn 'message'
28
+ expect(received).to be_truthy
25
29
  end
26
30
 
27
31
  context 'formatter' do
@@ -132,10 +136,13 @@ describe Ably::Logger do
132
136
  end
133
137
 
134
138
  it 'delegates log messages to logger', :api_private do
135
- expect(custom_logger_object).to receive(:fatal) do |*args, &block|
139
+ received = false
140
+ allow(custom_logger_object).to receive(:fatal) do |*args, &block|
136
141
  expect(args.concat([block ? block.call : nil]).join(',')).to match(/message/)
142
+ received = true
137
143
  end
138
144
  subject.fatal 'message'
145
+ expect(received).to be_truthy
139
146
  end
140
147
  end
141
148
  end
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+
5
+ describe Ably::Models::DeviceDetails do
6
+ include Ably::Modules::Conversions
7
+
8
+ subject { Ably::Models::DeviceDetails }
9
+
10
+ %w(id platform form_factor client_id device_secret).each do |string_attribute|
11
+ let(:empty_device_details) { subject.new }
12
+
13
+ describe "##{string_attribute} and ##{string_attribute}=" do
14
+ let(:new_val) { random_str }
15
+
16
+ specify 'setter accepts a string value and getter returns the new value' do
17
+ expect(empty_device_details.public_send(string_attribute)).to be_nil
18
+ empty_device_details.public_send("#{string_attribute}=", new_val)
19
+ expect(empty_device_details.public_send(string_attribute)).to eql(new_val)
20
+ end
21
+
22
+ specify 'setter accepts nil' do
23
+ empty_device_details.public_send("#{string_attribute}=", new_val)
24
+ expect(empty_device_details.public_send(string_attribute)).to eql(new_val)
25
+ empty_device_details.public_send("#{string_attribute}=", nil)
26
+ expect(empty_device_details.public_send(string_attribute)).to be_nil
27
+ end
28
+
29
+ specify 'rejects non string or nil values' do
30
+ expect { empty_device_details.public_send("#{string_attribute}=", {}) }.to raise_error(ArgumentError)
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'camelCase constructor attributes' do
36
+ let(:client_id) { random_str }
37
+ let(:device_details) { subject.new("clientId" => client_id ) }
38
+
39
+ specify 'are rubyfied and exposed as underscore case' do
40
+ expect(device_details.client_id).to eql(client_id)
41
+ end
42
+
43
+ specify 'are generated when the object is serialised to JSON' do
44
+ expect(JSON.parse(device_details.to_json)["clientId"]).to eql(client_id)
45
+ end
46
+ end
47
+
48
+ describe "#metadata and #metadata=" do
49
+ let(:new_val) { { foo: random_str } }
50
+
51
+ specify 'setter accepts a Hash value and getter returns the new value' do
52
+ expect(empty_device_details.metadata).to eql({})
53
+ empty_device_details.metadata = new_val
54
+ expect(empty_device_details.metadata.to_json).to eql(new_val.to_json)
55
+ end
56
+
57
+ specify 'setter accepts nil but always returns an empty hash' do
58
+ empty_device_details.metadata = new_val
59
+ expect(empty_device_details.metadata.to_json).to eql(new_val.to_json)
60
+ empty_device_details.metadata = nil
61
+ expect(empty_device_details.metadata).to eql({})
62
+ end
63
+
64
+ specify 'rejects non Hash or nil values' do
65
+ expect { empty_device_details.metadata = "foo" }.to raise_error(ArgumentError)
66
+ end
67
+ end
68
+
69
+ describe "#push and #push=" do
70
+ let(:transport_type) { random_str }
71
+ let(:new_val) { { recipient: { transport_type: transport_type } } }
72
+ let(:json_val) { { recipient: { transportType: transport_type } }.to_json }
73
+
74
+ specify 'setter accepts a DevicePushDetails object and getter returns a DevicePushDetails object' do
75
+ expect(empty_device_details.push.to_json).to eql({}.to_json)
76
+ empty_device_details.push = DevicePushDetails(new_val)
77
+ expect(empty_device_details.push).to be_a(Ably::Models::DevicePushDetails)
78
+ expect(empty_device_details.push.recipient[:transport_type]).to eql(transport_type)
79
+ expect(empty_device_details.push.to_json).to eql(json_val)
80
+ end
81
+
82
+ specify 'setter accepts a Hash value and getter returns a DevicePushDetails object' do
83
+ expect(empty_device_details.push.to_json).to eql({}.to_json)
84
+ empty_device_details.push = new_val
85
+ expect(empty_device_details.push).to be_a(Ably::Models::DevicePushDetails)
86
+ expect(empty_device_details.push.recipient[:transport_type]).to eql(transport_type)
87
+ expect(empty_device_details.push.to_json).to eql(json_val)
88
+ end
89
+
90
+ specify 'setter accepts nil but always returns a DevicePushDetails object' do
91
+ empty_device_details.push = new_val
92
+ expect(empty_device_details.push.to_json).to eql(json_val)
93
+ empty_device_details.push = nil
94
+ expect(empty_device_details.push).to be_a(Ably::Models::DevicePushDetails)
95
+ expect(empty_device_details.push.to_json).to eql({}.to_json)
96
+ end
97
+
98
+ specify 'rejects non Hash, DevicePushDetails or nil values' do
99
+ expect { empty_device_details.metadata = "foo" }.to raise_error(ArgumentError)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+
5
+ describe Ably::Models::DevicePushDetails do
6
+ include Ably::Modules::Conversions
7
+
8
+ subject { Ably::Models::DevicePushDetails }
9
+
10
+ %w(state).each do |string_attribute|
11
+ let(:empty_push_details) { subject.new }
12
+
13
+ describe "##{string_attribute} and ##{string_attribute}=" do
14
+ let(:new_val) { random_str }
15
+
16
+ specify 'setter accepts a string value and getter returns the new value' do
17
+ expect(empty_push_details.public_send(string_attribute)).to be_nil
18
+ empty_push_details.public_send("#{string_attribute}=", new_val)
19
+ expect(empty_push_details.public_send(string_attribute)).to eql(new_val)
20
+ end
21
+
22
+ specify 'setter accepts nil' do
23
+ empty_push_details.public_send("#{string_attribute}=", new_val)
24
+ expect(empty_push_details.public_send(string_attribute)).to eql(new_val)
25
+ empty_push_details.public_send("#{string_attribute}=", nil)
26
+ expect(empty_push_details.public_send(string_attribute)).to be_nil
27
+ end
28
+
29
+ specify 'rejects non string or nil values' do
30
+ expect { empty_push_details.public_send("#{string_attribute}=", {}) }.to raise_error(ArgumentError)
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'camelCase constructor attributes' do
36
+ let(:transport_type) { random_str }
37
+ let(:push_details) { subject.new('errorReason' => { 'message' => 'foo' }, 'recipient' => { 'transportType' => transport_type }) }
38
+
39
+ specify 'are rubyfied and exposed as underscore case' do
40
+ expect(push_details.recipient[:transport_type]).to eql(transport_type)
41
+ expect(push_details.error_reason.message).to eql('foo')
42
+ end
43
+
44
+ specify 'are generated when the object is serialised to JSON' do
45
+ expect(JSON.parse(push_details.to_json)['recipient']['transportType']).to eql(transport_type)
46
+ end
47
+ end
48
+
49
+ describe "#recipient and #recipient=" do
50
+ let(:new_val) { { foo: random_str } }
51
+
52
+ specify 'setter accepts a Hash value and getter returns the new value' do
53
+ expect(empty_push_details.recipient).to eql({})
54
+ empty_push_details.recipient = new_val
55
+ expect(empty_push_details.recipient.to_json).to eql(new_val.to_json)
56
+ end
57
+
58
+ specify 'setter accepts nil but always returns an empty hash' do
59
+ empty_push_details.recipient = new_val
60
+ expect(empty_push_details.recipient.to_json).to eql(new_val.to_json)
61
+ empty_push_details.recipient = nil
62
+ expect(empty_push_details.recipient).to eql({})
63
+ end
64
+
65
+ specify 'rejects non Hash or nil values' do
66
+ expect { empty_push_details.recipient = "foo" }.to raise_error(ArgumentError)
67
+ end
68
+ end
69
+
70
+ describe "#error_reason and #error_reason=" do
71
+ let(:error_message) { random_str }
72
+ let(:error_attributes) { { message: error_message } }
73
+
74
+ specify 'setter accepts a ErrorInfo object and getter returns a ErrorInfo object' do
75
+ expect(empty_push_details.error_reason).to be_nil
76
+ empty_push_details.error_reason = ErrorInfo(error_attributes)
77
+ expect(empty_push_details.error_reason).to be_a(Ably::Models::ErrorInfo)
78
+ expect(empty_push_details.error_reason.message).to eql(error_message)
79
+ expect(empty_push_details.error_reason.to_json).to eql(error_attributes.to_json)
80
+ end
81
+
82
+ specify 'setter accepts a Hash value and getter returns a ErrorInfo object' do
83
+ expect(empty_push_details.error_reason).to be_nil
84
+ empty_push_details.error_reason = error_attributes
85
+ expect(empty_push_details.error_reason).to be_a(Ably::Models::ErrorInfo)
86
+ expect(empty_push_details.error_reason.message).to eql(error_message)
87
+ expect(empty_push_details.error_reason.to_json).to eql(error_attributes.to_json)
88
+ end
89
+
90
+ specify 'setter accepts nil values' do
91
+ empty_push_details.error_reason = error_attributes
92
+ expect(empty_push_details.error_reason.to_json).to eql(error_attributes.to_json)
93
+ empty_push_details.error_reason = nil
94
+ expect(empty_push_details.error_reason).to be_nil
95
+ end
96
+
97
+ specify 'rejects non Hash, ErrorInfo or nil values' do
98
+ expect { empty_push_details.error_reason = "foo" }.to raise_error(ArgumentError)
99
+ end
100
+ end
101
+ end
@@ -4,15 +4,63 @@ require 'shared/model_behaviour'
4
4
  describe Ably::Models::ErrorInfo do
5
5
  subject { Ably::Models::ErrorInfo }
6
6
 
7
- it_behaves_like 'a model', with_simple_attributes: %w(code status_code message) do
8
- let(:model_args) { [] }
7
+ context '#TI1, #TI4' do
8
+ it_behaves_like 'a model', with_simple_attributes: %w(code status_code href message) do
9
+ let(:model_args) { [] }
10
+ end
9
11
  end
10
12
 
11
- context '#status' do
13
+ context '#status #TI1, #TI2' do
12
14
  subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
13
15
  it 'is an alias for #status_code' do
14
16
  expect(subject.status).to eql(subject.status_code)
15
17
  expect(subject.status).to eql(401)
16
18
  end
17
19
  end
20
+
21
+ context 'log entries container help link #TI5' do
22
+ context 'without an error code' do
23
+ subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
24
+
25
+ it 'does not include the help URL' do
26
+ expect(subject.to_s.scan(/help\.ably\.io/)).to be_empty
27
+ end
28
+ end
29
+
30
+ context 'with a specified error code' do
31
+ subject { Ably::Models::ErrorInfo.new('code' => 44444) }
32
+
33
+ it 'includes https://help.ably.io/error/[CODE] in the stringified object' do
34
+ expect(subject.to_s).to include('https://help.ably.io/error/44444')
35
+ end
36
+ end
37
+
38
+ context 'with an error code and an href attribute' do
39
+ subject { Ably::Models::ErrorInfo.new('code' => 44444, 'href' => 'http://foo.bar.com/') }
40
+
41
+ it 'includes the specified href in the stringified object' do
42
+ expect(subject.to_s).to include('http://foo.bar.com/')
43
+ expect(subject.to_s).to_not include('https://help.ably.io/error/44444')
44
+ end
45
+ end
46
+
47
+ context 'with an error code and a message with the same error URL' do
48
+ subject { Ably::Models::ErrorInfo.new('message' => 'error https://help.ably.io/error/44444', 'code' => 44444) }
49
+
50
+ it 'includes the specified error URL only once in the stringified object' do
51
+ expect(subject.to_s.scan(/help.ably.io/).length).to eql(1)
52
+ end
53
+ end
54
+
55
+ context 'with an error code and a message with a different error URL' do
56
+ subject { Ably::Models::ErrorInfo.new('message' => 'error https://help.ably.io/error/123123', 'code' => 44444) }
57
+
58
+ it 'includes the specified error URL from the message and the error code URL in the stringified object' do
59
+ puts subject.to_s
60
+ expect(subject.to_s.scan(/help.ably.io/).length).to eql(2)
61
+ expect(subject.to_s.scan(%r{error/123123}).length).to eql(1)
62
+ expect(subject.to_s.scan(%r{error/44444}).length).to eql(1)
63
+ end
64
+ end
65
+ end
18
66
  end
@@ -12,8 +12,23 @@ describe Ably::Models::Message do
12
12
  let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
13
13
  let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
14
14
 
15
- it_behaves_like 'a model', with_simple_attributes: %w(id name client_id data encoding) do
16
- let(:model_args) { [protocol_message: protocol_message] }
15
+ context 'serialization of the Message object (#RSL1j)' do
16
+ it_behaves_like 'a model', with_simple_attributes: %w(id name client_id data encoding) do
17
+ let(:model_args) { [protocol_message: protocol_message] }
18
+ end
19
+ end
20
+
21
+ context '#id (#RSL1j)' do
22
+ let(:id) { random_str }
23
+ let(:model) { subject.new(id: id) }
24
+
25
+ it 'exposes the #id attribute' do
26
+ expect(model.id).to eql(id)
27
+ end
28
+
29
+ specify '#as_json exposes the #id attribute' do
30
+ expect(model.as_json['id']).to eql(id)
31
+ end
17
32
  end
18
33
 
19
34
  context '#timestamp' do
@@ -547,7 +547,7 @@ describe Ably::Models::PresenceMessage do
547
547
  clone = model.shallow_clone(id: 'newId', action: 1, timestamp: protocol_message_timestamp + 1000)
548
548
  expect(clone.id).to match(/newId/)
549
549
  expect(clone.connection_id).to eql(protocol_connection_id)
550
- expect(as_since_epoch(clone.timestamp)).to eq(protocol_message_timestamp + 1000)
550
+ expect(as_since_epoch(clone.timestamp)).to be_within(5).of(protocol_message_timestamp + 1000)
551
551
  expect(clone.action).to eq(1)
552
552
  end
553
553
 
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+
5
+ describe Ably::Models::PushChannelSubscription do
6
+ include Ably::Modules::Conversions
7
+
8
+ subject { Ably::Models::PushChannelSubscription }
9
+
10
+ %w(channel client_id device_id).each do |string_attribute|
11
+ describe "##{string_attribute} and ##{string_attribute}=" do
12
+ let(:empty_device_details) do
13
+ if string_attribute == 'device_id'
14
+ subject.new(channel: 'default', device_id: 'default')
15
+ else
16
+ subject.new(channel: 'default', client_id: 'default')
17
+ end
18
+ end
19
+ let(:new_val) { random_str }
20
+
21
+ specify 'setter accepts a string value and getter returns the new value' do
22
+ expect(empty_device_details.public_send(string_attribute)).to eql('default')
23
+ empty_device_details.public_send("#{string_attribute}=", new_val)
24
+ expect(empty_device_details.public_send(string_attribute)).to eql(new_val)
25
+ end
26
+
27
+ specify 'setter accepts nil' do
28
+ empty_device_details.public_send("#{string_attribute}=", new_val)
29
+ expect(empty_device_details.public_send(string_attribute)).to eql(new_val)
30
+ empty_device_details.public_send("#{string_attribute}=", nil)
31
+ expect(empty_device_details.public_send(string_attribute)).to be_nil
32
+ end
33
+
34
+ specify 'rejects non string or nil values' do
35
+ expect { empty_device_details.public_send("#{string_attribute}=", {}) }.to raise_error(ArgumentError)
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'camelCase constructor attributes' do
41
+ let(:client_id) { random_str }
42
+ let(:device_details) { subject.new(channel: 'foo', 'clientId' => client_id ) }
43
+
44
+ specify 'are rubyfied and exposed as underscore case' do
45
+ expect(device_details.client_id).to eql(client_id)
46
+ end
47
+
48
+ specify 'are generated when the object is serialised to JSON' do
49
+ expect(JSON.parse(device_details.to_json)["clientId"]).to eql(client_id)
50
+ end
51
+ end
52
+
53
+ describe 'conversion method PushChannelSubscription' do
54
+ let(:channel) { 'foo' }
55
+ let(:device_id) { 'bar' }
56
+
57
+ it 'accepts a PushChannelSubscription object' do
58
+ push_channel_sub = PushChannelSubscription(channel: channel, device_id: device_id)
59
+ expect(push_channel_sub.channel).to eql('foo')
60
+ expect(push_channel_sub.client_id).to be_nil
61
+ expect(push_channel_sub.device_id).to eql('bar')
62
+ end
63
+ end
64
+
65
+ describe '#for_client_id constructor' do
66
+ context 'with a valid object' do
67
+ let(:channel) { 'foo' }
68
+ let(:client_id) { 'bob' }
69
+
70
+ it 'accepts a Hash object' do
71
+ push_channel_sub = Ably::Models::PushChannelSubscription.for_client_id(channel, client_id)
72
+ expect(push_channel_sub.channel).to eql('foo')
73
+ expect(push_channel_sub.client_id).to eql('bob')
74
+ expect(push_channel_sub.device_id).to be_nil
75
+ end
76
+ end
77
+
78
+ context 'with an invalid valid object' do
79
+ let(:subscription) { { channel: 'foo' } }
80
+
81
+ it 'accepts a Hash object' do
82
+ expect { Ably::Models::PushChannelSubscription.for_device(subscription) }.to raise_error(ArgumentError)
83
+ end
84
+ end
85
+ end
86
+ end