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