ably 0.1.6 → 0.2.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +8 -1
  6. data/Rakefile +10 -0
  7. data/ably.gemspec +18 -18
  8. data/lib/ably.rb +6 -5
  9. data/lib/ably/auth.rb +11 -14
  10. data/lib/ably/exceptions.rb +18 -15
  11. data/lib/ably/logger.rb +102 -0
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/message.rb +19 -5
  14. data/lib/ably/models/message_encoders/base.rb +107 -0
  15. data/lib/ably/models/message_encoders/base64.rb +39 -0
  16. data/lib/ably/models/message_encoders/cipher.rb +80 -0
  17. data/lib/ably/models/message_encoders/json.rb +33 -0
  18. data/lib/ably/models/message_encoders/utf8.rb +33 -0
  19. data/lib/ably/models/paginated_resource.rb +23 -6
  20. data/lib/ably/models/presence_message.rb +19 -7
  21. data/lib/ably/models/protocol_message.rb +5 -4
  22. data/lib/ably/models/token.rb +2 -2
  23. data/lib/ably/modules/channels_collection.rb +0 -3
  24. data/lib/ably/modules/conversions.rb +3 -3
  25. data/lib/ably/modules/encodeable.rb +68 -0
  26. data/lib/ably/modules/event_emitter.rb +10 -4
  27. data/lib/ably/modules/event_machine_helpers.rb +6 -4
  28. data/lib/ably/modules/http_helpers.rb +7 -2
  29. data/lib/ably/modules/model_common.rb +2 -0
  30. data/lib/ably/modules/state_emitter.rb +10 -1
  31. data/lib/ably/realtime.rb +19 -12
  32. data/lib/ably/realtime/channel.rb +26 -13
  33. data/lib/ably/realtime/client.rb +31 -7
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
  36. data/lib/ably/realtime/connection.rb +152 -46
  37. data/lib/ably/realtime/connection/connection_manager.rb +168 -0
  38. data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
  39. data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
  40. data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
  41. data/lib/ably/realtime/presence.rb +38 -13
  42. data/lib/ably/rest.rb +7 -5
  43. data/lib/ably/rest/channel.rb +24 -3
  44. data/lib/ably/rest/client.rb +56 -17
  45. data/lib/ably/rest/middleware/encoder.rb +49 -0
  46. data/lib/ably/rest/middleware/exceptions.rb +3 -2
  47. data/lib/ably/rest/middleware/logger.rb +37 -0
  48. data/lib/ably/rest/presence.rb +10 -2
  49. data/lib/ably/util/crypto.rb +57 -29
  50. data/lib/ably/util/pub_sub.rb +11 -0
  51. data/lib/ably/version.rb +1 -1
  52. data/spec/acceptance/realtime/channel_spec.rb +65 -7
  53. data/spec/acceptance/realtime/connection_spec.rb +123 -27
  54. data/spec/acceptance/realtime/message_spec.rb +319 -34
  55. data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
  56. data/spec/acceptance/realtime/presence_spec.rb +160 -18
  57. data/spec/acceptance/rest/auth_spec.rb +93 -49
  58. data/spec/acceptance/rest/base_spec.rb +10 -10
  59. data/spec/acceptance/rest/channel_spec.rb +35 -19
  60. data/spec/acceptance/rest/channels_spec.rb +8 -8
  61. data/spec/acceptance/rest/message_spec.rb +224 -0
  62. data/spec/acceptance/rest/presence_spec.rb +159 -23
  63. data/spec/acceptance/rest/stats_spec.rb +5 -5
  64. data/spec/acceptance/rest/time_spec.rb +4 -4
  65. data/spec/integration/rest/auth.rb +1 -1
  66. data/spec/resources/crypto-data-128.json +56 -0
  67. data/spec/resources/crypto-data-256.json +56 -0
  68. data/spec/rspec_config.rb +39 -0
  69. data/spec/spec_helper.rb +4 -42
  70. data/spec/support/api_helper.rb +1 -1
  71. data/spec/support/event_machine_helper.rb +0 -5
  72. data/spec/support/protocol_msgbus_helper.rb +3 -3
  73. data/spec/support/test_app.rb +3 -3
  74. data/spec/unit/logger_spec.rb +135 -0
  75. data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  76. data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  77. data/spec/unit/models/message_encoders/json_spec.rb +135 -0
  78. data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
  79. data/spec/unit/models/message_spec.rb +16 -1
  80. data/spec/unit/models/paginated_resource_spec.rb +46 -0
  81. data/spec/unit/models/presence_message_spec.rb +18 -5
  82. data/spec/unit/models/token_spec.rb +1 -1
  83. data/spec/unit/modules/event_emitter_spec.rb +24 -10
  84. data/spec/unit/realtime/channel_spec.rb +3 -3
  85. data/spec/unit/realtime/channels_spec.rb +1 -1
  86. data/spec/unit/realtime/client_spec.rb +44 -2
  87. data/spec/unit/realtime/connection_spec.rb +2 -2
  88. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
  89. data/spec/unit/realtime/presence_spec.rb +1 -1
  90. data/spec/unit/realtime/realtime_spec.rb +3 -3
  91. data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
  92. data/spec/unit/rest/channels_spec.rb +1 -1
  93. data/spec/unit/rest/client_spec.rb +45 -10
  94. data/spec/unit/util/crypto_spec.rb +82 -0
  95. data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
  96. metadata +43 -12
  97. data/spec/acceptance/crypto.rb +0 -63
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ably/models/message_encoders/utf8'
4
+
5
+ describe Ably::Models::MessageEncoders::Utf8 do
6
+ let(:string_ascii) { 'string'.force_encoding(Encoding::ASCII_8BIT) }
7
+ let(:string_utf8) { 'string'.force_encoding(Encoding::UTF_8) }
8
+
9
+ let(:client) { instance_double('Ably::Realtime::Client') }
10
+
11
+ subject { Ably::Models::MessageEncoders::Utf8.new(client) }
12
+
13
+ context '#decode' do
14
+ before do
15
+ subject.decode message, {}
16
+ end
17
+
18
+ context 'message with utf8 payload' do
19
+ let(:message) { { data: string_ascii, encoding: 'utf-8' } }
20
+
21
+ it 'sets the encoding' do
22
+ expect(message[:data]).to eq(string_utf8)
23
+ expect(message[:data].encoding).to eql(Encoding::UTF_8)
24
+ end
25
+
26
+ it 'strips the encoding' do
27
+ expect(message[:encoding]).to be_nil
28
+ end
29
+ end
30
+
31
+ context 'message with utf8 payload before other payloads' do
32
+ let(:message) { { data: string_utf8, encoding: 'json/utf-8' } }
33
+
34
+ it 'sets the encoding' do
35
+ expect(message[:data]).to eql(string_utf8)
36
+ expect(message[:data].encoding).to eql(Encoding::UTF_8)
37
+ end
38
+
39
+ it 'strips the encoding' do
40
+ expect(message[:encoding]).to eql('json')
41
+ end
42
+ end
43
+
44
+ context 'message with another payload' do
45
+ let(:message) { { data: string_ascii, encoding: 'json' } }
46
+
47
+ it 'leaves the message data intact' do
48
+ expect(message[:data]).to eql(string_ascii)
49
+ end
50
+
51
+ it 'leaves the encoding intact' do
52
+ expect(message[:encoding]).to eql('json')
53
+ end
54
+ end
55
+ end
56
+
57
+ context '#encode' do
58
+ before do
59
+ subject.encode message, {}
60
+ end
61
+
62
+ context 'message with json payload' do
63
+ let(:message) { { data: string_ascii, encoding: 'json' } }
64
+
65
+ it 'sets the cencoding' do
66
+ expect(message[:data]).to eql(string_utf8)
67
+ expect(message[:data].encoding).to eql(Encoding::UTF_8)
68
+ end
69
+
70
+ it 'adds the encoding' do
71
+ expect(message[:encoding]).to eql('json/utf-8')
72
+ end
73
+ end
74
+
75
+
76
+ context 'message with string payload and no encoding' do
77
+ let(:message) { { data: string_ascii, encoding: nil } }
78
+
79
+ it 'leaves the message data intact' do
80
+ expect(message[:data]).to eql(string_ascii)
81
+ end
82
+
83
+ it 'leaves the encoding intact' do
84
+ expect(message[:encoding]).to eql(nil)
85
+ end
86
+ end
87
+
88
+ context 'message with string payload and UTF-8 encoding' do
89
+ let(:message) { { data: string_ascii, encoding: 'utf-8' } }
90
+
91
+ it 'leaves the message data intact' do
92
+ expect(message[:data]).to eql(string_ascii)
93
+ end
94
+
95
+ it 'leaves the encoding intact' do
96
+ expect(message[:encoding]).to eql('utf-8')
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,5 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'support/model_helper'
3
+ require 'base64'
4
+ require 'msgpack'
3
5
 
4
6
  describe Ably::Models::Message do
5
7
  include Ably::Modules::Conversions
@@ -8,7 +10,7 @@ describe Ably::Models::Message do
8
10
  let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
9
11
  let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
10
12
 
11
- it_behaves_like 'a model', with_simple_attributes: %w(name client_id data) do
13
+ it_behaves_like 'a model', with_simple_attributes: %w(name client_id data encoding) do
12
14
  let(:model_args) { [protocol_message] }
13
15
  end
14
16
 
@@ -46,6 +48,19 @@ describe Ably::Models::Message do
46
48
  expect { model.to_json }.to raise_error RuntimeError, /cannot generate a valid Hash/
47
49
  end
48
50
  end
51
+
52
+ context 'with binary data' do
53
+ let(:data) { MessagePack.pack(SecureRandom.hex(32)) }
54
+ let(:model) { subject.new({ name: 'test', data: data }, protocol_message) }
55
+
56
+ it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
57
+ expect(json_object["data"]).to eql(::Base64.encode64(data))
58
+ end
59
+
60
+ it 'adds Base64 encoding' do
61
+ expect(json_object["encoding"]).to eql('base64')
62
+ end
63
+ end
49
64
  end
50
65
 
51
66
  context 'from REST request with embedded fields' do
@@ -67,6 +67,52 @@ describe Ably::Models::PaginatedResource do
67
67
  end
68
68
  end
69
69
 
70
+ context 'with each block' do
71
+ let(:headers) do
72
+ {
73
+ 'link' => [
74
+ '<./history?index=1>; rel="next"'
75
+ ].join(', ')
76
+ }
77
+ end
78
+ let(:paged_client) do
79
+ instance_double('Ably::Rest::Client').tap do |client|
80
+ allow(client).to receive(:get).and_return(http_response_page2)
81
+ end
82
+ end
83
+ let(:body_page2) do
84
+ [
85
+ { id: 2 },
86
+ { id: 3 }
87
+ ]
88
+ end
89
+ let(:http_response_page2) do
90
+ instance_double('Faraday::Response', {
91
+ body: body_page2,
92
+ headers: headers
93
+ })
94
+ end
95
+
96
+ subject do
97
+ paginated_resource_class.new(http_response, full_url, paged_client, paginated_resource_options) do |resource|
98
+ resource[:added_attribute_from_block] = "id:#{resource[:id]}"
99
+ resource
100
+ end
101
+ end
102
+
103
+ it 'calls the block for each resource after retrieving the resources' do
104
+ expect(subject[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
105
+ end
106
+
107
+ it 'calls the block for each resource on second page after retrieving the resources' do
108
+ page_1_first_id = subject[0][:id]
109
+ next_page = subject.next_page
110
+
111
+ expect(next_page[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
112
+ expect(next_page[0][:id]).to_not eql(page_1_first_id)
113
+ end
114
+ end
115
+
70
116
  context 'with non paged http response' do
71
117
  it 'is the first page' do
72
118
  expect(subject).to be_first_page
@@ -8,7 +8,7 @@ describe Ably::Models::PresenceMessage do
8
8
  let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
9
9
  let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
10
10
 
11
- it_behaves_like 'a model', with_simple_attributes: %w(client_id member_id client_data) do
11
+ it_behaves_like 'a model', with_simple_attributes: %w(client_id member_id data encoding) do
12
12
  let(:model_args) { [protocol_message] }
13
13
  end
14
14
 
@@ -62,6 +62,19 @@ describe Ably::Models::PresenceMessage do
62
62
  expect { model.to_json }.to raise_error KeyError, /cannot generate a valid Hash/
63
63
  end
64
64
  end
65
+
66
+ context 'with binary data' do
67
+ let(:data) { MessagePack.pack(SecureRandom.hex(32)) }
68
+ let(:model) { subject.new({ action: 'enter', data: data }, protocol_message) }
69
+
70
+ it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
71
+ expect(json_object["data"]).to eql(::Base64.encode64(data))
72
+ end
73
+
74
+ it 'adds Base64 encoding' do
75
+ expect(json_object["encoding"]).to eql('base64')
76
+ end
77
+ end
65
78
  end
66
79
 
67
80
  context 'from REST request with embedded fields' do
@@ -100,14 +113,14 @@ describe Ably::Models::PresenceMessage do
100
113
  let(:presence_0_json) do
101
114
  {
102
115
  client_id: 'zero',
103
- client_data: presence_0_payload
116
+ data: presence_0_payload
104
117
  }
105
118
  end
106
119
  let(:presence_1_payload) { SecureRandom.hex(8) }
107
120
  let(:presence_1_json) do
108
121
  {
109
122
  client_id: 'one',
110
- client_data: presence_1_payload
123
+ data: presence_1_payload
111
124
  }
112
125
  end
113
126
 
@@ -133,8 +146,8 @@ describe Ably::Models::PresenceMessage do
133
146
  end
134
147
 
135
148
  it 'should not modify the data payload' do
136
- expect(presence_0.client_data).to eql(presence_0_payload)
137
- expect(presence_1.client_data).to eql(presence_1_payload)
149
+ expect(presence_0.data).to eql(presence_0_payload)
150
+ expect(presence_1.data).to eql(presence_1_payload)
138
151
  end
139
152
  end
140
153
 
@@ -1,4 +1,4 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Ably::Models::Token do
4
4
  subject { Ably::Models::Token }
@@ -95,19 +95,33 @@ describe Ably::Modules::EventEmitter do
95
95
  subject.trigger :message, msg
96
96
  end
97
97
 
98
- it 'deletes matching callbacks' do
99
- expect(obj).to_not receive(:received_message).with(msg)
100
- subject.off(:message, &callback)
101
- end
98
+ context 'with event names as arguments' do
99
+ it 'deletes matching callbacks' do
100
+ expect(obj).to_not receive(:received_message).with(msg)
101
+ subject.off(:message, &callback)
102
+ end
103
+
104
+ it 'deletes all callbacks if not block given' do
105
+ expect(obj).to_not receive(:received_message).with(msg)
106
+ subject.off(:message)
107
+ end
102
108
 
103
- it 'deletes all callbacks if not block given' do
104
- expect(obj).to_not receive(:received_message).with(msg)
105
- subject.off(:message)
109
+ it 'continues if the block does not exist' do
110
+ expect(obj).to receive(:received_message).with(msg)
111
+ subject.off(:message) { true }
112
+ end
106
113
  end
107
114
 
108
- it 'continues if the block does not exist' do
109
- expect(obj).to receive(:received_message).with(msg)
110
- subject.off(:message) { true }
115
+ context 'without any event names' do
116
+ it 'deletes all matching callbacks' do
117
+ expect(obj).to_not receive(:received_message).with(msg)
118
+ subject.off(&callback)
119
+ end
120
+
121
+ it 'deletes all callbacks if not block given' do
122
+ expect(obj).to_not receive(:received_message).with(msg)
123
+ subject.off
124
+ end
111
125
  end
112
126
  end
113
127
  end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
- require "support/protocol_msgbus_helper"
2
+ require 'support/protocol_msgbus_helper'
3
3
 
4
4
  describe Ably::Realtime::Channel do
5
- let(:client) { double('client').as_null_object }
5
+ let(:client) { double('client').as_null_object }
6
6
  let(:channel_name) { 'test' }
7
7
 
8
8
  subject do
@@ -48,7 +48,7 @@ describe Ably::Realtime::Channel do
48
48
  context 'subscriptions' do
49
49
  let(:message_history) { Hash.new { |hash, key| hash[key] = 0 } }
50
50
  let(:event_name) { 'click' }
51
- let(:message) { instance_double('Ably::Models::Message', name: event_name) }
51
+ let(:message) { instance_double('Ably::Models::Message', name: event_name, encode: nil, decode: nil) }
52
52
 
53
53
  context '#subscribe' do
54
54
  specify 'to all events' do
@@ -1,4 +1,4 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Ably::Realtime::Channels do
4
4
  let(:connection) { instance_double('Ably::Realtime::Connection', on: true) }
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require "support/protocol_msgbus_helper"
2
+ require 'support/protocol_msgbus_helper'
3
3
 
4
4
  describe Ably::Realtime::Client do
5
5
  let(:client_options) { 'appid.keyuid:keysecret' }
@@ -27,12 +27,54 @@ describe Ably::Realtime::Client do
27
27
  end
28
28
 
29
29
  context 'for attribute' do
30
- [:environment, :use_tls?, :logger, :log_level].each do |attribute|
30
+ [:environment, :use_tls?, :log_level].each do |attribute|
31
31
  specify "##{attribute}" do
32
32
  expect(subject.rest_client).to receive(attribute)
33
33
  subject.public_send attribute
34
34
  end
35
35
  end
36
36
  end
37
+
38
+ context 'logger' do
39
+ context 'defaults' do
40
+ let(:logger) { subject.logger }
41
+
42
+ subject { Ably::Realtime::Client.new(client_options) }
43
+
44
+ it 'uses default Ruby Logger by default' do
45
+ expect(subject.logger.logger).to be_a(::Logger)
46
+ end
47
+
48
+ it 'defaults to Logger::ERROR log level' do
49
+ expect(subject.logger.log_level).to eql(::Logger::ERROR)
50
+ end
51
+
52
+ it 'returns the connection ID' do
53
+ allow(subject).to receive_message_chain(:connection, :id).and_return('AAA')
54
+ expect(logger.logger.formatter.call(0, Time.now, '', 'unique_message')).to match(/AAA/)
55
+ end
56
+ end
57
+
58
+ context 'with custom logger and log_level' do
59
+ let(:custom_logger) do
60
+ Class.new do
61
+ extend Forwardable
62
+ def initialize
63
+ @logger = Logger.new(STDOUT)
64
+ end
65
+ def_delegators :@logger, :fatal, :error, :warn, :info, :debug, :level, :level=
66
+ end
67
+ end
68
+ subject { Ably::Realtime::Client.new(api_key: 'appid.keyuid:keysecret', logger: custom_logger.new, log_level: :debug) }
69
+
70
+ it 'uses the custom logger' do
71
+ expect(subject.logger.logger.class).to eql(custom_logger)
72
+ end
73
+
74
+ it 'sets the custom log level' do
75
+ expect(subject.logger.log_level).to eql(Logger::DEBUG)
76
+ end
77
+ end
78
+ end
37
79
  end
38
80
  end
@@ -10,8 +10,8 @@ describe Ably::Realtime::Connection do
10
10
  end
11
11
 
12
12
  before do
13
- expect(EventMachine::Timer).to receive(:new)
14
- expect(EventMachine).to receive(:next_tick)
13
+ expect(EventMachine::Timer).to receive(:new) # Connection Manager #initializer
14
+ expect(EventMachine).to receive(:next_tick) # non_blocking_loop_while for delivery of messages async
15
15
  end
16
16
 
17
17
  describe 'callbacks' do
@@ -5,7 +5,7 @@ describe Ably::Realtime::Client::IncomingMessageDispatcher do
5
5
  Ably::Util::PubSub.new
6
6
  end
7
7
  let(:connection) do
8
- instance_double('Ably::Realtime::Connection', __incoming_protocol_msgbus__: msgbus, update_connection_id: true)
8
+ instance_double('Ably::Realtime::Connection', __incoming_protocol_msgbus__: msgbus, update_connection_id: true, id: nil)
9
9
  end
10
10
  let(:client) do
11
11
  instance_double('Ably::Realtime::Client', channels: {})
@@ -15,7 +15,7 @@ describe Ably::Realtime::Client::IncomingMessageDispatcher do
15
15
 
16
16
  context '#initialize' do
17
17
  it 'should subscribe to protocol messages from the connection' do
18
- expect(msgbus).to receive(:subscribe).with(:message).and_call_original
18
+ expect(msgbus).to receive(:subscribe).with(:protocol_message).and_call_original
19
19
  subject
20
20
  end
21
21
  end
@@ -24,13 +24,13 @@ describe Ably::Realtime::Client::IncomingMessageDispatcher do
24
24
  before { subject }
25
25
 
26
26
  it 'should raise an exception if a message is sent that is not a ProtocolMessage' do
27
- expect { msgbus.publish :message, nil }.to raise_error ArgumentError
27
+ expect { msgbus.publish :protocol_message, nil }.to raise_error ArgumentError
28
28
  end
29
29
 
30
30
  it 'should warn if a message is received for a non-existent channel' do
31
31
  allow(subject).to receive_message_chain(:logger, :debug)
32
32
  expect(subject).to receive_message_chain(:logger, :warn)
33
- msgbus.publish :message, Ably::Models::ProtocolMessage.new(:action => :attached, channel: 'unknown')
33
+ msgbus.publish :protocol_message, Ably::Models::ProtocolMessage.new(:action => :attached, channel: 'unknown')
34
34
  end
35
35
  end
36
36
  end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require "support/protocol_msgbus_helper"
2
+ require 'support/protocol_msgbus_helper'
3
3
 
4
4
  describe Ably::Realtime::Presence do
5
5
  let(:channel) { double('Ably::Realtime::Channel').as_null_object }
@@ -1,4 +1,4 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Ably::Realtime do
4
4
  let(:options) { { api_key: 'app.key:secret' } }
@@ -8,8 +8,8 @@ describe Ably::Realtime do
8
8
  end
9
9
 
10
10
  describe Ably::Realtime::Client do
11
- describe "initializing the client" do
12
- it "should disallow an invalid key" do
11
+ describe 'initializing the client' do
12
+ it 'should disallow an invalid key' do
13
13
  expect { Ably::Realtime::Client.new({}) }.to raise_error(ArgumentError, /api_key is missing/)
14
14
  expect { Ably::Realtime::Client.new(api_key: 'invalid') }.to raise_error(ArgumentError, /api_key is invalid/)
15
15
  expect { Ably::Realtime::Client.new(api_key: 'invalid:asdad') }.to raise_error(ArgumentError, /api_key is invalid/)