ably-rest 0.7.1 → 0.7.3
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.
- checksums.yaml +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Rest do
|
|
5
|
+
describe 'transport protocol' do
|
|
6
|
+
include Ably::Modules::Conversions
|
|
7
|
+
|
|
8
|
+
let(:client_options) { {} }
|
|
9
|
+
let(:client) do
|
|
10
|
+
Ably::Rest::Client.new(client_options.merge(api_key: 'appid.keyuid:keysecret'))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
let(:now) { Time.now - 1000 }
|
|
14
|
+
let(:body_value) { [as_since_epoch(now)] }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
stub_request(:get, "#{client.endpoint}/time").
|
|
18
|
+
with(:headers => { 'Accept' => mime }).
|
|
19
|
+
to_return(:status => 200, :body => request_body, :headers => { 'Content-Type' => mime })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'when protocol is not defined it defaults to :msgpack' do
|
|
23
|
+
let(:client_options) { { } }
|
|
24
|
+
let(:mime) { 'application/x-msgpack' }
|
|
25
|
+
let(:request_body) { body_value.to_msgpack }
|
|
26
|
+
|
|
27
|
+
it 'uses MsgPack', :webmock do
|
|
28
|
+
expect(client.protocol).to eql(:msgpack)
|
|
29
|
+
expect(client.time).to be_within(1).of(now)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
options = [
|
|
34
|
+
{ protocol: :json },
|
|
35
|
+
{ use_binary_protocol: false }
|
|
36
|
+
].each do |client_option|
|
|
37
|
+
|
|
38
|
+
context "when option #{client_option} is used" do
|
|
39
|
+
let(:client_options) { client_option }
|
|
40
|
+
let(:mime) { 'application/json' }
|
|
41
|
+
let(:request_body) { body_value.to_json }
|
|
42
|
+
|
|
43
|
+
it 'uses JSON', :webmock do
|
|
44
|
+
expect(client.protocol).to eql(:json)
|
|
45
|
+
expect(client.time).to be_within(1).of(now)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
options = [
|
|
51
|
+
{ protocol: :msgpack },
|
|
52
|
+
{ use_binary_protocol: true }
|
|
53
|
+
].each do |client_option|
|
|
54
|
+
|
|
55
|
+
context "when option #{client_option} is used" do
|
|
56
|
+
let(:client_options) { client_option }
|
|
57
|
+
let(:mime) { 'application/x-msgpack' }
|
|
58
|
+
let(:request_body) { body_value.to_msgpack }
|
|
59
|
+
|
|
60
|
+
it 'uses MsgPack', :webmock do
|
|
61
|
+
expect(client.protocol).to eql(:msgpack)
|
|
62
|
+
expect(client.time).to be_within(1).of(now)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
vary_by_protocol do
|
|
69
|
+
let(:client) do
|
|
70
|
+
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'failed requests' do
|
|
74
|
+
context 'due to invalid Auth' do
|
|
75
|
+
it 'should raise an InvalidRequest exception with a valid error message and code' do
|
|
76
|
+
invalid_client = Ably::Rest::Client.new(api_key: 'appid.keyuid:keysecret', environment: environment)
|
|
77
|
+
expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error|
|
|
78
|
+
expect(error).to be_a(Ably::Exceptions::InvalidRequest)
|
|
79
|
+
expect(error.message).to match(/invalid credentials/)
|
|
80
|
+
expect(error.code).to eql(40100)
|
|
81
|
+
expect(error.status).to eql(401)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe 'server error with JSON error response body', :webmock do
|
|
87
|
+
let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
|
|
88
|
+
|
|
89
|
+
before do
|
|
90
|
+
stub_request(:get, "#{client.endpoint}/time").
|
|
91
|
+
to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'should raise a ServerError exception' do
|
|
95
|
+
expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Internal error/)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '500 server error without a valid JSON response body', :webmock do
|
|
100
|
+
before do
|
|
101
|
+
stub_request(:get, "#{client.endpoint}/time").
|
|
102
|
+
to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'should raise a ServerError exception' do
|
|
106
|
+
expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Unknown/)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe 'token authentication failures', :webmock do
|
|
112
|
+
let(:token_1) { { id: random_str } }
|
|
113
|
+
let(:token_2) { { id: random_str } }
|
|
114
|
+
let(:channel) { 'channelname' }
|
|
115
|
+
|
|
116
|
+
before do
|
|
117
|
+
@token_requests = 0
|
|
118
|
+
@publish_attempts = 0
|
|
119
|
+
|
|
120
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").to_return do
|
|
121
|
+
@token_requests += 1
|
|
122
|
+
{
|
|
123
|
+
:body => { access_token: send("token_#{@token_requests}").merge(expires: Time.now.to_i + 3600) }.to_json,
|
|
124
|
+
:headers => { 'Content-Type' => 'application/json' }
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
stub_request(:post, "#{client.endpoint}/channels/#{channel}/publish").to_return do
|
|
129
|
+
@publish_attempts += 1
|
|
130
|
+
if [1, 3].include?(@publish_attempts)
|
|
131
|
+
{ status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } }
|
|
132
|
+
else
|
|
133
|
+
raise Ably::Exceptions::InvalidRequest.new('Authentication failure', 401, 40140)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context 'when auth#token_renewable?' do
|
|
139
|
+
before do
|
|
140
|
+
client.auth.authorise
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'should automatically reissue a token' do
|
|
144
|
+
client.channel(channel).publish('evt', 'msg')
|
|
145
|
+
expect(@publish_attempts).to eql(1)
|
|
146
|
+
|
|
147
|
+
client.channel(channel).publish('evt', 'msg')
|
|
148
|
+
expect(@publish_attempts).to eql(3)
|
|
149
|
+
expect(@token_requests).to eql(2)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context 'when NOT auth#token_renewable?' do
|
|
154
|
+
let(:client) { Ably::Rest::Client.new(token_id: 'token ID cannot be used to create a new token', environment: environment, protocol: protocol) }
|
|
155
|
+
|
|
156
|
+
it 'should raise an InvalidToken exception' do
|
|
157
|
+
client.channel(channel).publish('evt', 'msg')
|
|
158
|
+
expect(@publish_attempts).to eql(1)
|
|
159
|
+
expect { client.channel(channel).publish('evt', 'msg') }.to raise_error Ably::Exceptions::InvalidToken
|
|
160
|
+
expect(@token_requests).to eql(0)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Rest::Channel do
|
|
5
|
+
include Ably::Modules::Conversions
|
|
6
|
+
|
|
7
|
+
vary_by_protocol do
|
|
8
|
+
let(:client) do
|
|
9
|
+
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#publish' do
|
|
13
|
+
let(:channel) { client.channel('test') }
|
|
14
|
+
let(:event) { 'foo' }
|
|
15
|
+
let(:message) { 'woop!' }
|
|
16
|
+
|
|
17
|
+
it 'should publish the message adn return true indicating success' do
|
|
18
|
+
expect(channel.publish(event, message)).to eql(true)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#history' do
|
|
23
|
+
let(:channel) { client.channel("persisted:#{random_str(4)}") }
|
|
24
|
+
let(:expected_history) do
|
|
25
|
+
[
|
|
26
|
+
{ :name => 'test1', :data => 'foo' },
|
|
27
|
+
{ :name => 'test2', :data => 'bar' },
|
|
28
|
+
{ :name => 'test3', :data => 'baz' }
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
let!(:before_published) { client.time }
|
|
32
|
+
|
|
33
|
+
before(:each) do
|
|
34
|
+
expected_history.each do |message|
|
|
35
|
+
channel.publish(message[:name], message[:data]) || raise('Unable to publish message')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'should return the current message history for the channel' do
|
|
40
|
+
actual_history = channel.history
|
|
41
|
+
|
|
42
|
+
expect(actual_history.size).to eql(3)
|
|
43
|
+
|
|
44
|
+
expected_history.each do |message|
|
|
45
|
+
message_name, message_data = message[:name], message[:data]
|
|
46
|
+
matching_message = actual_history.find { |message| message.name == message_name && message.data == message_data }
|
|
47
|
+
expect(matching_message).to be_a(Ably::Models::Message)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'message timestamps' do
|
|
52
|
+
it 'should all be after the messages were published' do
|
|
53
|
+
channel.history.each do |message|
|
|
54
|
+
expect(before_published.to_f).to be < message.timestamp.to_f
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'message IDs' do
|
|
60
|
+
it 'should be unique' do
|
|
61
|
+
message_ids = channel.history.map(&:id).compact
|
|
62
|
+
expect(message_ids.count).to eql(3)
|
|
63
|
+
expect(message_ids.uniq.count).to eql(3)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'should return paged history using the PaginatedResource model' do
|
|
68
|
+
page_1 = channel.history(limit: 1)
|
|
69
|
+
page_2 = page_1.next_page
|
|
70
|
+
page_3 = page_2.next_page
|
|
71
|
+
|
|
72
|
+
all_items = [page_1[0].id, page_2[0].id, page_3[0].id]
|
|
73
|
+
expect(all_items.uniq).to eql(all_items)
|
|
74
|
+
|
|
75
|
+
expect(page_1.size).to eql(1)
|
|
76
|
+
expect(page_1).to_not be_last_page
|
|
77
|
+
expect(page_1).to be_first_page
|
|
78
|
+
|
|
79
|
+
# Page 2
|
|
80
|
+
expect(page_2.size).to eql(1)
|
|
81
|
+
expect(page_2).to_not be_last_page
|
|
82
|
+
expect(page_2).to_not be_first_page
|
|
83
|
+
|
|
84
|
+
# Page 3
|
|
85
|
+
expect(page_3.size).to eql(1)
|
|
86
|
+
expect(page_3).to be_last_page
|
|
87
|
+
expect(page_3).to_not be_first_page
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#history option' do
|
|
92
|
+
let(:channel_name) { "persisted:#{random_str(4)}" }
|
|
93
|
+
let(:channel) { client.channel(channel_name) }
|
|
94
|
+
let(:endpoint) do
|
|
95
|
+
client.endpoint.tap do |client_end_point|
|
|
96
|
+
client_end_point.user = key_id
|
|
97
|
+
client_end_point.password = key_secret
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
[:start, :end].each do |option|
|
|
102
|
+
describe ":#{option}", :webmock do
|
|
103
|
+
let!(:history_stub) {
|
|
104
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{option}=#{milliseconds}").
|
|
105
|
+
to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
before do
|
|
109
|
+
channel.history(options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context 'with milliseconds since epoch value' do
|
|
113
|
+
let(:milliseconds) { as_since_epoch(Time.now) }
|
|
114
|
+
let(:options) { { option => milliseconds } }
|
|
115
|
+
|
|
116
|
+
it 'uses this value in the history request' do
|
|
117
|
+
expect(history_stub).to have_been_requested
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context 'with a Time object value' do
|
|
122
|
+
let(:time) { Time.now }
|
|
123
|
+
let(:milliseconds) { as_since_epoch(time) }
|
|
124
|
+
let(:options) { { option => time } }
|
|
125
|
+
|
|
126
|
+
it 'converts the value to milliseconds since epoch in the hisotry request' do
|
|
127
|
+
expect(history_stub).to have_been_requested
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Rest::Channels do
|
|
5
|
+
shared_examples 'a channel' do
|
|
6
|
+
it 'returns a channel object' do
|
|
7
|
+
expect(channel).to be_a Ably::Rest::Channel
|
|
8
|
+
expect(channel.name).to eql(channel_name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'returns channel object and passes the provided options' do
|
|
12
|
+
expect(channel_with_options.options).to eql(options)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
vary_by_protocol do
|
|
17
|
+
let(:client) do
|
|
18
|
+
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
19
|
+
end
|
|
20
|
+
let(:channel_name) { random_str }
|
|
21
|
+
let(:options) { { key: 'value' } }
|
|
22
|
+
|
|
23
|
+
describe 'using shortcut method #channel on the client object' do
|
|
24
|
+
let(:channel) { client.channel(channel_name) }
|
|
25
|
+
let(:channel_with_options) { client.channel(channel_name, options) }
|
|
26
|
+
it_behaves_like 'a channel'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe 'using #get method on client#channels' do
|
|
30
|
+
let(:channel) { client.channels.get(channel_name) }
|
|
31
|
+
let(:channel_with_options) { client.channels.get(channel_name, options) }
|
|
32
|
+
it_behaves_like 'a channel'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe 'using undocumented array accessor [] method on client#channels' do
|
|
36
|
+
let(:channel) { client.channels[channel_name] }
|
|
37
|
+
let(:channel_with_options) { client.channels[channel_name, options] }
|
|
38
|
+
it_behaves_like 'a channel'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Rest::Client do
|
|
5
|
+
vary_by_protocol do
|
|
6
|
+
let(:default_options) { { environment: environment, protocol: protocol } }
|
|
7
|
+
let(:client_options) { default_options }
|
|
8
|
+
|
|
9
|
+
let(:client) { Ably::Rest::Client.new(client_options) }
|
|
10
|
+
|
|
11
|
+
connection_retry = Ably::Rest::Client::CONNECTION_RETRY
|
|
12
|
+
|
|
13
|
+
context '#initialize' do
|
|
14
|
+
let(:client_id) { random_str }
|
|
15
|
+
let(:token_request) { client.auth.create_token_request(key_id: key_id, key_secret: key_secret, client_id: client_id) }
|
|
16
|
+
|
|
17
|
+
context 'with an auth block' do
|
|
18
|
+
let(:client) { Ably::Rest::Client.new(client_options) { token_request } }
|
|
19
|
+
|
|
20
|
+
it 'calls the block to get a new token' do
|
|
21
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
|
|
22
|
+
expect(client.auth.current_token.client_id).to eql(client_id)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'with an auth URL' do
|
|
27
|
+
let(:client_options) { default_options.merge(auth_url: token_request_url, auth_method: :get) }
|
|
28
|
+
let(:token_request_url) { 'http://get.token.request.com/' }
|
|
29
|
+
|
|
30
|
+
before do
|
|
31
|
+
allow(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, :auth_method => :get).and_return(token_request)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'sends an HTTP request to the provided URL to get a new token' do
|
|
35
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
|
|
36
|
+
expect(client.auth.current_token.client_id).to eql(client_id)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'using tokens' do
|
|
42
|
+
let(:client) do
|
|
43
|
+
Ably::Rest::Client.new(client_options) do
|
|
44
|
+
@request_index ||= 0
|
|
45
|
+
@request_index += 1
|
|
46
|
+
send("token_request_#{@request_index}")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
let(:token_request_1) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
|
|
50
|
+
let(:token_request_2) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
|
|
51
|
+
|
|
52
|
+
context 'when expired' do
|
|
53
|
+
let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: Ably::Models::Token::TOKEN_EXPIRY_BUFFER } }
|
|
54
|
+
|
|
55
|
+
it 'creates a new token automatically when the old token expires' do
|
|
56
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
|
|
57
|
+
expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
|
|
58
|
+
|
|
59
|
+
sleep 1
|
|
60
|
+
|
|
61
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
|
|
62
|
+
expect(client.auth.current_token.client_id).to eql(token_request_2['clientId'])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'when token has not expired' do
|
|
67
|
+
let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: 3600 } }
|
|
68
|
+
|
|
69
|
+
it 'reuses the existing token for every request' do
|
|
70
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
|
|
71
|
+
expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
|
|
72
|
+
|
|
73
|
+
sleep 1
|
|
74
|
+
|
|
75
|
+
expect { client.channel('channel_name').publish('event', 'message') }.to_not change { client.auth.current_token }
|
|
76
|
+
expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'connection transport' do
|
|
82
|
+
let(:client_options) { default_options.merge(api_key: api_key) }
|
|
83
|
+
|
|
84
|
+
context 'for default host' do
|
|
85
|
+
it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
|
|
86
|
+
expect(client.connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
|
|
90
|
+
expect(client.connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'for the fallback hosts' do
|
|
95
|
+
it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
|
|
96
|
+
expect(client.fallback_connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
|
|
100
|
+
expect(client.fallback_connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'fallback hosts', :webmock do
|
|
106
|
+
let(:path) { '/channels/test/publish' }
|
|
107
|
+
let(:publish_block) { proc { client.channel('test').publish('event', 'data') } }
|
|
108
|
+
|
|
109
|
+
context 'configured' do
|
|
110
|
+
let(:client_options) { default_options.merge(api_key: api_key) }
|
|
111
|
+
|
|
112
|
+
it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com' do
|
|
113
|
+
hosts = []
|
|
114
|
+
5.times do
|
|
115
|
+
hosts << client.fallback_connection.host
|
|
116
|
+
end
|
|
117
|
+
expect(hosts).to match_array(%w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com))
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context 'when environment is NOT production' do
|
|
122
|
+
let(:client_options) { default_options.merge(environment: 'sandbox', api_key: api_key) }
|
|
123
|
+
let!(:default_host_request_stub) do
|
|
124
|
+
stub_request(:post, "https://#{api_key}@#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
|
|
125
|
+
raise Faraday::TimeoutError.new('timeout error message')
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'does not retry failed requests with fallback hosts when there is a connection error' do
|
|
130
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'when environment is production' do
|
|
135
|
+
let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
|
|
136
|
+
let(:max_attempts) { 2 }
|
|
137
|
+
let(:cumulative_timeout) { 0.5 }
|
|
138
|
+
let(:client_options) { default_options.merge(environment: nil, api_key: api_key) }
|
|
139
|
+
|
|
140
|
+
before do
|
|
141
|
+
stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
|
|
142
|
+
stub_const 'Ably::Rest::Client::CONNECTION_RETRY', {
|
|
143
|
+
single_request_open_timeout: 4,
|
|
144
|
+
single_request_timeout: 15,
|
|
145
|
+
cumulative_request_open_timeout: cumulative_timeout,
|
|
146
|
+
max_retry_attempts: max_attempts
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
let!(:first_fallback_request_stub) do
|
|
151
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return do
|
|
152
|
+
raise Faraday::SSLError.new('ssl error message')
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
let!(:second_fallback_request_stub) do
|
|
157
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return do
|
|
158
|
+
raise Faraday::SSLError.new('ssl error message')
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'and connection times out' do
|
|
163
|
+
let!(:default_host_request_stub) do
|
|
164
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
|
|
165
|
+
raise Faraday::TimeoutError.new('timeout error message')
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
|
|
170
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
|
|
171
|
+
expect(default_host_request_stub).to have_been_requested
|
|
172
|
+
expect(first_fallback_request_stub).to have_been_requested
|
|
173
|
+
expect(second_fallback_request_stub).to have_been_requested
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "and the total request time exeeds #{connection_retry[:cumulative_request_open_timeout]} seconds" do
|
|
177
|
+
let!(:default_host_request_stub) do
|
|
178
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
|
|
179
|
+
sleep cumulative_timeout * 1.5
|
|
180
|
+
raise Faraday::TimeoutError.new('timeout error message')
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'makes no further attempts to any fallback hosts' do
|
|
185
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
|
|
186
|
+
expect(default_host_request_stub).to have_been_requested
|
|
187
|
+
expect(first_fallback_request_stub).to_not have_been_requested
|
|
188
|
+
expect(second_fallback_request_stub).to_not have_been_requested
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'and connection fails' do
|
|
194
|
+
let!(:default_host_request_stub) do
|
|
195
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
|
|
196
|
+
raise Faraday::ConnectionFailed.new('connection failure error message')
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
|
|
201
|
+
expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
|
|
202
|
+
expect(default_host_request_stub).to have_been_requested
|
|
203
|
+
expect(first_fallback_request_stub).to have_been_requested
|
|
204
|
+
expect(second_fallback_request_stub).to have_been_requested
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context 'with a custom host' do
|
|
211
|
+
let(:custom_host) { 'host.does.not.exist' }
|
|
212
|
+
let(:client_options) { default_options.merge(api_key: api_key, rest_host: custom_host) }
|
|
213
|
+
let(:capability) { { :foo => ["publish"] } }
|
|
214
|
+
|
|
215
|
+
context 'that does not exist' do
|
|
216
|
+
it 'fails immediately and raises a Faraday Error' do
|
|
217
|
+
expect { client.channel('test').publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionError
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context 'fallback hosts', :webmock do
|
|
221
|
+
let(:path) { '/channels/test/publish' }
|
|
222
|
+
|
|
223
|
+
let!(:custom_host_request_stub) do
|
|
224
|
+
stub_request(:post, "https://#{api_key}@#{custom_host}#{path}").to_return do
|
|
225
|
+
raise Faraday::ConnectionFailed.new('connection failure error message')
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
before do
|
|
230
|
+
Ably::FALLBACK_HOSTS.each do |host|
|
|
231
|
+
stub_request(:post, "https://#{host}#{path}").to_return do
|
|
232
|
+
raise 'Fallbacks should not be used with custom hosts'
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
specify 'are never used' do
|
|
238
|
+
expect { client.channel('test').publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionError
|
|
239
|
+
expect(custom_host_request_stub).to have_been_requested
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context 'that times out', :webmock do
|
|
245
|
+
let(:path) { '/keys/app_id.key_id/requestToken' }
|
|
246
|
+
let!(:custom_host_request_stub) do
|
|
247
|
+
stub_request(:post, "https://#{custom_host}#{path}").to_return do
|
|
248
|
+
raise Faraday::TimeoutError.new('timeout error message')
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it 'fails immediately and raises a Faraday Error' do
|
|
253
|
+
expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
context 'fallback hosts' do
|
|
257
|
+
before do
|
|
258
|
+
Ably::FALLBACK_HOSTS.each do |host|
|
|
259
|
+
stub_request(:post, "https://#{host}#{path}").to_return do
|
|
260
|
+
raise 'Fallbacks should not be used with custom hosts'
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
specify 'are never used' do
|
|
266
|
+
expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
|
|
267
|
+
expect(custom_host_request_stub).to have_been_requested
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|