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,23 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Ably::Realtime::Client, '#stats', :event_machine do
|
|
4
|
+
vary_by_protocol do
|
|
5
|
+
let(:client) do
|
|
6
|
+
Ably::Realtime::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe 'fetching stats' do
|
|
10
|
+
it 'should return a PaginatedResource' do
|
|
11
|
+
client.stats do |stats|
|
|
12
|
+
expect(stats).to be_a(Ably::Models::PaginatedResource)
|
|
13
|
+
stop_reactor
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should return a Deferrable object' do
|
|
18
|
+
expect(client.stats).to be_a(EventMachine::Deferrable)
|
|
19
|
+
stop_reactor
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Ably::Realtime::Client, '#time', :event_machine do
|
|
4
|
+
vary_by_protocol do
|
|
5
|
+
let(:client) do
|
|
6
|
+
Ably::Realtime::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe 'fetching the service time' do
|
|
10
|
+
it 'should return the service time as a Time object' do
|
|
11
|
+
run_reactor do
|
|
12
|
+
client.time do |time|
|
|
13
|
+
expect(time).to be_within(2).of(Time.now)
|
|
14
|
+
stop_reactor
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should return a deferrable object' do
|
|
20
|
+
run_reactor do
|
|
21
|
+
expect(client.time).to be_a(EventMachine::Deferrable)
|
|
22
|
+
stop_reactor
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Auth do
|
|
5
|
+
include Ably::Modules::Conversions
|
|
6
|
+
|
|
7
|
+
def hmac_for(token_request, secret)
|
|
8
|
+
ruby_named_token_request = Ably::Models::IdiomaticRubyWrapper.new(token_request)
|
|
9
|
+
|
|
10
|
+
text = [
|
|
11
|
+
:id,
|
|
12
|
+
:ttl,
|
|
13
|
+
:capability,
|
|
14
|
+
:client_id,
|
|
15
|
+
:timestamp,
|
|
16
|
+
:nonce
|
|
17
|
+
].map { |key| "#{ruby_named_token_request[key]}\n" }.join("")
|
|
18
|
+
|
|
19
|
+
encode64(
|
|
20
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
vary_by_protocol do
|
|
25
|
+
let(:client) do
|
|
26
|
+
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
|
27
|
+
end
|
|
28
|
+
let(:auth) { client.auth }
|
|
29
|
+
let(:content_type) do
|
|
30
|
+
if protocol == :msgpack
|
|
31
|
+
'application/x-msgpack'
|
|
32
|
+
else
|
|
33
|
+
'application/json'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def request_body_includes(request, protocol, key, val)
|
|
38
|
+
body = if protocol == :msgpack
|
|
39
|
+
MessagePack.unpack(request.body)
|
|
40
|
+
else
|
|
41
|
+
JSON.parse(request.body)
|
|
42
|
+
end
|
|
43
|
+
body[convert_to_mixed_case(key)].to_s == val.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def serialize(object, protocol)
|
|
47
|
+
if protocol == :msgpack
|
|
48
|
+
MessagePack.pack(token_response)
|
|
49
|
+
else
|
|
50
|
+
JSON.dump(token_response)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'has immutable options' do
|
|
55
|
+
expect { auth.options['key_id'] = 'new_id' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#request_token' do
|
|
59
|
+
let(:ttl) { 30 * 60 }
|
|
60
|
+
let(:capability) { { :foo => ['publish'] } }
|
|
61
|
+
|
|
62
|
+
let(:token) do
|
|
63
|
+
auth.request_token(
|
|
64
|
+
ttl: ttl,
|
|
65
|
+
capability: capability
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'returns a valid requested token in the expected format with valid issued_at and expires_at attributes' do
|
|
70
|
+
expect(token.id).to match(/^#{app_id}\.[\w-]+$/)
|
|
71
|
+
expect(token.key_id).to match(/^#{key_id}$/)
|
|
72
|
+
expect(token.issued_at).to be_within(2).of(Time.now)
|
|
73
|
+
expect(token.expires_at).to be_within(2).of(Time.now + ttl)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
%w(client_id capability nonce timestamp ttl).each do |option|
|
|
77
|
+
context "with option :#{option}", :webmock do
|
|
78
|
+
let(:random) { random_int_str }
|
|
79
|
+
let(:options) { { option.to_sym => random } }
|
|
80
|
+
|
|
81
|
+
let(:token_response) { { access_token: {} } }
|
|
82
|
+
let!(:request_token_stub) do
|
|
83
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
|
84
|
+
with do |request|
|
|
85
|
+
request_body_includes(request, protocol, option, random)
|
|
86
|
+
end.to_return(
|
|
87
|
+
:status => 201,
|
|
88
|
+
:body => serialize(token_response, protocol),
|
|
89
|
+
:headers => { 'Content-Type' => content_type }
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
before { auth.request_token options }
|
|
94
|
+
|
|
95
|
+
it 'overrides default and uses camelCase notation for all attributes' do
|
|
96
|
+
expect(request_token_stub).to have_been_requested
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context 'with :key_id & :key_secret options', :webmock do
|
|
102
|
+
let(:key_id) { random_str }
|
|
103
|
+
let(:key_secret) { random_str }
|
|
104
|
+
let(:nonce) { random_str }
|
|
105
|
+
let(:token_options) { { key_id: key_id, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
|
|
106
|
+
let(:token_request) { auth.create_token_request(token_options) }
|
|
107
|
+
let(:mac) do
|
|
108
|
+
hmac_for(token_request, key_secret)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
let(:token_response) { { access_token: {} } }
|
|
112
|
+
let!(:request_token_stub) do
|
|
113
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
|
114
|
+
with do |request|
|
|
115
|
+
request_body_includes(request, protocol, 'mac', mac)
|
|
116
|
+
end.to_return(
|
|
117
|
+
:status => 201,
|
|
118
|
+
:body => serialize(token_response, protocol),
|
|
119
|
+
:headers => { 'Content-Type' => content_type })
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
let!(:token) { auth.request_token(token_options) }
|
|
123
|
+
|
|
124
|
+
specify 'key_id is used in request and signing uses key_secret' do
|
|
125
|
+
expect(request_token_stub).to have_been_requested
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
context 'with :query_time option' do
|
|
130
|
+
let(:options) { { query_time: true } }
|
|
131
|
+
|
|
132
|
+
it 'queries the server for the time' do
|
|
133
|
+
expect(client).to receive(:time).and_call_original
|
|
134
|
+
auth.request_token(options)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context 'without :query_time option' do
|
|
139
|
+
let(:options) { { query_time: false } }
|
|
140
|
+
|
|
141
|
+
it 'does not query the server for the time' do
|
|
142
|
+
expect(client).to_not receive(:time)
|
|
143
|
+
auth.request_token(options)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
context 'with :auth_url option', :webmock do
|
|
148
|
+
let(:auth_url) { 'https://www.fictitious.com/get_token' }
|
|
149
|
+
let(:token_request) { { id: key_id } }
|
|
150
|
+
let(:token_response) { { access_token: { } } }
|
|
151
|
+
let(:query_params) { nil }
|
|
152
|
+
let(:headers) { nil }
|
|
153
|
+
let(:auth_method) { :get }
|
|
154
|
+
let(:options) do
|
|
155
|
+
{
|
|
156
|
+
auth_url: auth_url,
|
|
157
|
+
auth_params: query_params,
|
|
158
|
+
auth_headers: headers,
|
|
159
|
+
auth_method: auth_method
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
let!(:auth_url_request_stub) do
|
|
164
|
+
stub = stub_request(auth_method, auth_url)
|
|
165
|
+
stub.with(:query => query_params) unless query_params.nil?
|
|
166
|
+
stub.with(:headers => headers) unless headers.nil?
|
|
167
|
+
stub.to_return(
|
|
168
|
+
:status => 201,
|
|
169
|
+
:body => token_request.to_json,
|
|
170
|
+
:headers => { 'Content-Type' => 'application/json' }
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
let!(:request_token_stub) do
|
|
175
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
|
176
|
+
with do |request|
|
|
177
|
+
request_body_includes(request, protocol, 'id', key_id)
|
|
178
|
+
end.to_return(
|
|
179
|
+
:status => 201,
|
|
180
|
+
:body => serialize(token_response, protocol),
|
|
181
|
+
:headers => { 'Content-Type' => content_type }
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context 'when response is valid' do
|
|
186
|
+
before { auth.request_token options }
|
|
187
|
+
|
|
188
|
+
it 'requests a token from :auth_url using an HTTP GET request' do
|
|
189
|
+
expect(request_token_stub).to have_been_requested
|
|
190
|
+
expect(auth_url_request_stub).to have_been_requested
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'with :query_params' do
|
|
194
|
+
let(:query_params) { { 'key' => random_str } }
|
|
195
|
+
|
|
196
|
+
it 'requests a token from :auth_url with the :query_params' do
|
|
197
|
+
expect(request_token_stub).to have_been_requested
|
|
198
|
+
expect(auth_url_request_stub).to have_been_requested
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'with :headers' do
|
|
203
|
+
let(:headers) { { 'key' => random_str } }
|
|
204
|
+
it 'requests a token from :auth_url with the HTTP headers set' do
|
|
205
|
+
expect(request_token_stub).to have_been_requested
|
|
206
|
+
expect(auth_url_request_stub).to have_been_requested
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context 'with POST' do
|
|
211
|
+
let(:auth_method) { :post }
|
|
212
|
+
it 'requests a token from :auth_url using an HTTP POST instead of the default GET' do
|
|
213
|
+
expect(request_token_stub).to have_been_requested
|
|
214
|
+
expect(auth_url_request_stub).to have_been_requested
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
context 'when response is invalid' do
|
|
220
|
+
context '500' do
|
|
221
|
+
let!(:auth_url_request_stub) do
|
|
222
|
+
stub_request(auth_method, auth_url).to_return(:status => 500)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'raises ServerError' do
|
|
226
|
+
expect { auth.request_token options }.to raise_error(Ably::Exceptions::ServerError)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context 'XML' do
|
|
231
|
+
let!(:auth_url_request_stub) do
|
|
232
|
+
stub_request(auth_method, auth_url).
|
|
233
|
+
to_return(:status => 201, :body => '<xml></xml>', :headers => { 'Content-Type' => 'application/xml' })
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'raises InvalidResponseBody' do
|
|
237
|
+
expect { auth.request_token options }.to raise_error(Ably::Exceptions::InvalidResponseBody)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
context 'with token_request_block' do
|
|
244
|
+
let(:client_id) { random_str }
|
|
245
|
+
let(:options) { { client_id: client_id } }
|
|
246
|
+
let!(:token) do
|
|
247
|
+
auth.request_token(options) do |block_options|
|
|
248
|
+
@block_called = true
|
|
249
|
+
@block_options = block_options
|
|
250
|
+
auth.create_token_request(client_id: client_id)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'calls the block when authenticating to obtain the request token' do
|
|
255
|
+
expect(@block_called).to eql(true)
|
|
256
|
+
expect(@block_options).to include(options)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'uses the token request from the block when requesting a new token' do
|
|
260
|
+
expect(token.client_id).to eql(client_id)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
context 'before #authorise has been called' do
|
|
266
|
+
it 'has no current_token' do
|
|
267
|
+
expect(auth.current_token).to be_nil
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe '#authorise' do
|
|
272
|
+
context 'when called for the first time since the client has been instantiated' do
|
|
273
|
+
let(:request_options) do
|
|
274
|
+
{ auth_url: 'http://somewhere.com/' }
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'passes all options to #request_token' do
|
|
278
|
+
expect(auth).to receive(:request_token).with(request_options)
|
|
279
|
+
auth.authorise request_options
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'returns a valid token' do
|
|
283
|
+
expect(auth.authorise).to be_a(Ably::Models::Token)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it 'issues a new token if option :force => true' do
|
|
287
|
+
expect { auth.authorise(force: true) }.to change { auth.current_token }
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
context 'with previous authorisation' do
|
|
292
|
+
before do
|
|
293
|
+
auth.authorise
|
|
294
|
+
expect(auth.current_token).to_not be_expired
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'does not request a token if current_token has not expired' do
|
|
298
|
+
expect(auth).to_not receive(:request_token)
|
|
299
|
+
auth.authorise
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'requests a new token if token is expired' do
|
|
303
|
+
allow(auth.current_token).to receive(:expired?).and_return(true)
|
|
304
|
+
expect(auth).to receive(:request_token)
|
|
305
|
+
expect { auth.authorise }.to change { auth.current_token }
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it 'issues a new token if option :force => true' do
|
|
309
|
+
expect { auth.authorise(force: true) }.to change { auth.current_token }
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'updates the persisted auth options thare are then used for subsequent authorise requests' do
|
|
314
|
+
expect(auth.options[:ttl]).to_not eql(26)
|
|
315
|
+
auth.authorise(ttl: 26)
|
|
316
|
+
expect(auth.options[:ttl]).to eql(26)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
context 'with token_request_block' do
|
|
320
|
+
let(:client_id) { random_str }
|
|
321
|
+
let!(:token) do
|
|
322
|
+
auth.authorise do
|
|
323
|
+
@block_called ||= 0
|
|
324
|
+
@block_called += 1
|
|
325
|
+
auth.create_token_request(client_id: client_id)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it 'calls the block' do
|
|
330
|
+
expect(@block_called).to eql(1)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'uses the token request returned from the block when requesting a new token' do
|
|
334
|
+
expect(token.client_id).to eql(client_id)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
context 'for every subsequent #request_token' do
|
|
338
|
+
context 'without a provided block' do
|
|
339
|
+
it 'calls the originally provided block' do
|
|
340
|
+
auth.request_token
|
|
341
|
+
expect(@block_called).to eql(2)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
context 'with a provided block' do
|
|
346
|
+
it 'does not call the originally provided block and calls the new #request_token block' do
|
|
347
|
+
auth.request_token { @request_block_called = true; auth.create_token_request }
|
|
348
|
+
expect(@block_called).to eql(1)
|
|
349
|
+
expect(@request_block_called).to eql(true)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
describe '#create_token_request' do
|
|
357
|
+
let(:ttl) { 60 * 60 }
|
|
358
|
+
let(:capability) { { :foo => ["publish"] } }
|
|
359
|
+
let(:options) { Hash.new }
|
|
360
|
+
subject { auth.create_token_request(options) }
|
|
361
|
+
|
|
362
|
+
it 'uses the key ID from the client' do
|
|
363
|
+
expect(subject['id']).to eql(key_id)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
it 'uses the default TTL' do
|
|
367
|
+
expect(subject['ttl']).to eql(Ably::Models::Token::DEFAULTS[:ttl])
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it 'uses the default capability' do
|
|
371
|
+
expect(subject['capability']).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
context 'the nonce' do
|
|
375
|
+
it 'is unique for every request' do
|
|
376
|
+
unique_nonces = 100.times.map { auth.create_token_request['nonce'] }
|
|
377
|
+
expect(unique_nonces.uniq.length).to eql(100)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
it 'is at least 16 characters' do
|
|
381
|
+
expect(subject['nonce'].length).to be >= 16
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
%w(ttl capability nonce timestamp client_id).each do |attribute|
|
|
386
|
+
context "with option :#{attribute}" do
|
|
387
|
+
let(:option_value) { random_int_str(1_000_000_000) }
|
|
388
|
+
before do
|
|
389
|
+
options[attribute.to_sym] = option_value
|
|
390
|
+
end
|
|
391
|
+
it "overrides default" do
|
|
392
|
+
expect(subject[convert_to_mixed_case(attribute)].to_s).to eql(option_value.to_s)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
context 'with additional invalid attributes' do
|
|
398
|
+
let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
|
|
399
|
+
specify 'are ignored' do
|
|
400
|
+
expect(subject.keys).to_not include(:is_not_used_by_token_request)
|
|
401
|
+
expect(subject.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
|
|
402
|
+
expect(subject.keys).to include('nonce')
|
|
403
|
+
expect(subject['nonce']).to eql('valid')
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
context 'when required fields are missing' do
|
|
408
|
+
let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
|
|
409
|
+
|
|
410
|
+
it 'should raise an exception if key secret is missing' do
|
|
411
|
+
expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
it 'should raise an exception if key id is missing' do
|
|
415
|
+
expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
context 'with :query_time option' do
|
|
420
|
+
let(:time) { Time.now - 30 }
|
|
421
|
+
let(:options) { { query_time: true } }
|
|
422
|
+
|
|
423
|
+
it 'queries the server for the timestamp' do
|
|
424
|
+
expect(client).to receive(:time).and_return(time)
|
|
425
|
+
expect(subject['timestamp']).to eql(time.to_i)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
context 'with :timestamp option' do
|
|
430
|
+
let(:token_request_time) { Time.now + 5 }
|
|
431
|
+
let(:options) { { timestamp: token_request_time } }
|
|
432
|
+
|
|
433
|
+
it 'uses the provided timestamp in the token request' do
|
|
434
|
+
expect(subject['timestamp']).to eql(token_request_time.to_i)
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
context 'signing' do
|
|
439
|
+
let(:options) do
|
|
440
|
+
{
|
|
441
|
+
id: random_str,
|
|
442
|
+
ttl: random_str,
|
|
443
|
+
capability: random_str,
|
|
444
|
+
client_id: random_str,
|
|
445
|
+
timestamp: random_int_str,
|
|
446
|
+
nonce: random_str
|
|
447
|
+
}
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it 'generates a valid HMAC' do
|
|
451
|
+
hmac = hmac_for(options, key_secret)
|
|
452
|
+
expect(subject['mac']).to eql(hmac)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
context 'using token authentication' do
|
|
458
|
+
let(:capability) { { :foo => ["publish"] } }
|
|
459
|
+
|
|
460
|
+
describe 'with :token_id option' do
|
|
461
|
+
let(:ttl) { 60 * 60 }
|
|
462
|
+
let(:token) do
|
|
463
|
+
auth.request_token(
|
|
464
|
+
ttl: ttl,
|
|
465
|
+
capability: capability
|
|
466
|
+
)
|
|
467
|
+
end
|
|
468
|
+
let(:token_id) { token.id }
|
|
469
|
+
let(:token_auth_client) do
|
|
470
|
+
Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
it 'authenticates successfully using the provided :token_id' do
|
|
474
|
+
expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it 'disallows publishing on unspecified capability channels' do
|
|
478
|
+
expect { token_auth_client.channel('bar').publish('event', 'data') }.to raise_error do |error|
|
|
479
|
+
expect(error).to be_a(Ably::Exceptions::InvalidRequest)
|
|
480
|
+
expect(error.status).to eql(401)
|
|
481
|
+
expect(error.code).to eql(40160)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it 'fails if timestamp is invalid' do
|
|
486
|
+
expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
|
|
487
|
+
expect(error).to be_a(Ably::Exceptions::InvalidRequest)
|
|
488
|
+
expect(error.status).to eql(401)
|
|
489
|
+
expect(error.code).to eql(40101)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
it 'cannot be renewed automatically' do
|
|
494
|
+
expect(token_auth_client.auth).to_not be_token_renewable
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
context 'when implicit as a result of using :client id' do
|
|
499
|
+
let(:client_id) { '999' }
|
|
500
|
+
let(:client) do
|
|
501
|
+
Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment, protocol: protocol)
|
|
502
|
+
end
|
|
503
|
+
let(:token_id) { 'unique-token-id' }
|
|
504
|
+
let(:token_response) do
|
|
505
|
+
{
|
|
506
|
+
access_token: {
|
|
507
|
+
id: token_id
|
|
508
|
+
}
|
|
509
|
+
}.to_json
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
context 'and requests to the Ably server are mocked', :webmock do
|
|
513
|
+
let!(:request_token_stub) do
|
|
514
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
|
515
|
+
to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
|
|
516
|
+
end
|
|
517
|
+
let!(:publish_message_stub) do
|
|
518
|
+
stub_request(:post, "#{client.endpoint}/channels/foo/publish").
|
|
519
|
+
with(headers: { 'Authorization' => "Bearer #{encode64(token_id)}" }).
|
|
520
|
+
to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
it 'will send a token request to the server' do
|
|
524
|
+
client.channel('foo').publish('event', 'data')
|
|
525
|
+
expect(request_token_stub).to have_been_requested
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
describe 'a token is created' do
|
|
530
|
+
let(:token) { client.auth.current_token }
|
|
531
|
+
|
|
532
|
+
it 'before a request is made' do
|
|
533
|
+
expect(token).to be_nil
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
it 'when a message is published' do
|
|
537
|
+
expect(client.channel('foo').publish('event', 'data')).to be_truthy
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
it 'with capability and TTL defaults' do
|
|
541
|
+
client.channel('foo').publish('event', 'data')
|
|
542
|
+
|
|
543
|
+
expect(token).to be_a(Ably::Models::Token)
|
|
544
|
+
capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]
|
|
545
|
+
capability = Hash[capability_with_str_key.keys.map(&:to_sym).zip(capability_with_str_key.values)]
|
|
546
|
+
expect(token.capability).to eq(capability)
|
|
547
|
+
expect(token.expires_at.to_i).to be_within(2).of(Time.now.to_i + Ably::Models::Token::DEFAULTS[:ttl])
|
|
548
|
+
expect(token.client_id).to eq(client_id)
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
context 'when using an :api_key and basic auth' do
|
|
555
|
+
specify '#using_token_auth? is false' do
|
|
556
|
+
expect(auth).to_not be_using_token_auth
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
specify '#using_basic_auth? is true' do
|
|
560
|
+
expect(auth).to be_using_basic_auth
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|