ably 1.0.6 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.travis.yml +10 -8
  4. data/CHANGELOG.md +75 -3
  5. data/LICENSE +1 -3
  6. data/README.md +12 -7
  7. data/Rakefile +32 -0
  8. data/SPEC.md +1277 -835
  9. data/ably.gemspec +14 -9
  10. data/lib/ably/auth.rb +30 -4
  11. data/lib/ably/exceptions.rb +10 -4
  12. data/lib/ably/logger.rb +7 -1
  13. data/lib/ably/models/channel_state_change.rb +1 -1
  14. data/lib/ably/models/connection_state_change.rb +1 -1
  15. data/lib/ably/models/device_details.rb +87 -0
  16. data/lib/ably/models/device_push_details.rb +86 -0
  17. data/lib/ably/models/error_info.rb +23 -2
  18. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  19. data/lib/ably/models/protocol_message.rb +32 -2
  20. data/lib/ably/models/push_channel_subscription.rb +89 -0
  21. data/lib/ably/modules/conversions.rb +1 -1
  22. data/lib/ably/modules/encodeable.rb +1 -1
  23. data/lib/ably/modules/exception_codes.rb +128 -0
  24. data/lib/ably/modules/model_common.rb +15 -2
  25. data/lib/ably/modules/state_machine.rb +2 -2
  26. data/lib/ably/realtime.rb +1 -0
  27. data/lib/ably/realtime/auth.rb +1 -1
  28. data/lib/ably/realtime/channel.rb +24 -102
  29. data/lib/ably/realtime/channel/channel_manager.rb +2 -6
  30. data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  31. data/lib/ably/realtime/channel/publisher.rb +74 -0
  32. data/lib/ably/realtime/channel/push_channel.rb +62 -0
  33. data/lib/ably/realtime/client.rb +91 -3
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  36. data/lib/ably/realtime/connection.rb +34 -20
  37. data/lib/ably/realtime/connection/connection_manager.rb +25 -9
  38. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  39. data/lib/ably/realtime/presence.rb +4 -4
  40. data/lib/ably/realtime/presence/members_map.rb +3 -3
  41. data/lib/ably/realtime/push.rb +40 -0
  42. data/lib/ably/realtime/push/admin.rb +61 -0
  43. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  44. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  45. data/lib/ably/rest.rb +1 -0
  46. data/lib/ably/rest/channel.rb +53 -17
  47. data/lib/ably/rest/channel/push_channel.rb +62 -0
  48. data/lib/ably/rest/client.rb +154 -32
  49. data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  50. data/lib/ably/rest/presence.rb +1 -0
  51. data/lib/ably/rest/push.rb +42 -0
  52. data/lib/ably/rest/push/admin.rb +54 -0
  53. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  54. data/lib/ably/rest/push/device_registrations.rb +103 -0
  55. data/lib/ably/version.rb +7 -2
  56. data/spec/acceptance/realtime/auth_spec.rb +245 -17
  57. data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  58. data/spec/acceptance/realtime/channel_spec.rb +177 -59
  59. data/spec/acceptance/realtime/client_spec.rb +153 -0
  60. data/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
  61. data/spec/acceptance/realtime/connection_spec.rb +129 -18
  62. data/spec/acceptance/realtime/message_spec.rb +36 -34
  63. data/spec/acceptance/realtime/presence_spec.rb +201 -167
  64. data/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  65. data/spec/acceptance/realtime/push_spec.rb +27 -0
  66. data/spec/acceptance/rest/auth_spec.rb +41 -3
  67. data/spec/acceptance/rest/base_spec.rb +2 -2
  68. data/spec/acceptance/rest/channel_spec.rb +79 -4
  69. data/spec/acceptance/rest/channels_spec.rb +6 -0
  70. data/spec/acceptance/rest/client_spec.rb +129 -10
  71. data/spec/acceptance/rest/message_spec.rb +158 -6
  72. data/spec/acceptance/rest/push_admin_spec.rb +952 -0
  73. data/spec/acceptance/rest/push_spec.rb +25 -0
  74. data/spec/acceptance/rest/time_spec.rb +1 -1
  75. data/spec/run_parallel_tests +33 -0
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/debug_failure_helper.rb +9 -5
  78. data/spec/support/test_app.rb +2 -2
  79. data/spec/unit/logger_spec.rb +10 -3
  80. data/spec/unit/models/device_details_spec.rb +102 -0
  81. data/spec/unit/models/device_push_details_spec.rb +101 -0
  82. data/spec/unit/models/error_info_spec.rb +51 -3
  83. data/spec/unit/models/message_spec.rb +17 -2
  84. data/spec/unit/models/presence_message_spec.rb +1 -1
  85. data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  86. data/spec/unit/modules/enum_spec.rb +1 -1
  87. data/spec/unit/realtime/client_spec.rb +13 -1
  88. data/spec/unit/realtime/connection_spec.rb +1 -1
  89. data/spec/unit/realtime/push_channel_spec.rb +36 -0
  90. data/spec/unit/rest/channel_spec.rb +8 -1
  91. data/spec/unit/rest/client_spec.rb +30 -0
  92. data/spec/unit/rest/push_channel_spec.rb +36 -0
  93. metadata +95 -26
@@ -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
+
@@ -4,7 +4,7 @@ def console(message)
4
4
  puts "\033[31m[#{Time.now.strftime('%H:%M:%S.%L')}]\033[0m \033[33m#{message}\033[0m"
5
5
  end
6
6
 
7
- unless RUBY_VERSION.match(/^1/)
7
+ unless RUBY_VERSION.match(/^1\./)
8
8
  require 'coveralls'
9
9
  Coveralls.wear!
10
10
  end
@@ -2,6 +2,8 @@ RSpec.configure do |config|
2
2
  config.before(:example) do |example|
3
3
  next if example.metadata[:prevent_log_stubbing]
4
4
 
5
+ log_mutex = Mutex.new
6
+
5
7
  @log_output = []
6
8
  %w(fatal error warn info debug).each do |method_name|
7
9
  allow_any_instance_of(Ably::Logger).to receive(method_name.to_sym).and_wrap_original do |method, *args, &block|
@@ -10,11 +12,13 @@ RSpec.configure do |config|
10
12
 
11
13
  prefix = "#{Time.now.strftime('%H:%M:%S.%L')} [\e[33m#{method_name}\e[0m] "
12
14
 
13
- begin
14
- args << block.call unless block.nil?
15
- @log_output << "#{prefix}#{args.compact.join(' ')}"
16
- rescue StandardError => e
17
- @log_output << "#{prefix}Failed to log block - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}}"
15
+ log_mutex.synchronize do
16
+ begin
17
+ args << block.call unless block.nil?
18
+ @log_output << "#{prefix}#{args.compact.join(' ')}"
19
+ rescue StandardError => e
20
+ @log_output << "#{prefix}Failed to log block - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}}"
21
+ end
18
22
  end
19
23
 
20
24
  # Call original
@@ -4,11 +4,11 @@ class TestApp
4
4
  TEST_RESOURCES_PATH = File.expand_path('../../../lib/submodules/ably-common/test-resources', __FILE__)
5
5
 
6
6
  # App configuration for test app
7
- # See https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json
7
+ # See https://github.com/ably/ably-common/blob/main/test-resources/test-app-setup.json
8
8
  APP_SPEC = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['post_apps']
9
9
 
10
10
  # Cipher details used for client_encoded presence data in test app
11
- # See https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json
11
+ # See https://github.com/ably/ably-common/blob/main/test-resources/test-app-setup.json
12
12
  APP_SPEC_CIPHER = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['cipher']
13
13
 
14
14
  # If an app has already been created and we need a new app, create a new test app
@@ -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