ably 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -1,30 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Ably::Realtime::Client stats' do
4
- include RSpec::EventMachine
5
-
6
- [:msgpack, :json].each do |protocol|
7
- context "over #{protocol}" do
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
- describe 'fetching stats' do
13
- it 'should return a Hash' do
14
- run_reactor do
15
- client.stats do |stats|
16
- expect(stats).to be_a(Array)
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
- it 'should return a deferrable object' do
23
- run_reactor do
24
- expect(client.stats).to be_a(EventMachine::Deferrable)
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 'Ably::Realtime::Client time' do
4
- include RSpec::EventMachine
5
-
6
- [:msgpack, :json].each do |protocol|
7
- context "over #{protocol}" do
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
- describe 'fetching the service time' do
13
- it 'should return the service time as a Time object' do
14
- run_reactor do
15
- client.time do |time|
16
- expect(time).to be_within(2).of(Time.now)
17
- stop_reactor
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
- it 'should return a deferrable object' do
23
- run_reactor do
24
- expect(client.time).to be_a(EventMachine::Deferrable)
25
- stop_reactor
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
- [:msgpack, :json].each do |protocol|
8
- context "over #{protocol}" do
9
- let(:client) do
10
- Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
11
- end
12
- let(:auth) { client.auth }
13
- let(:content_type) do
14
- if protocol == :msgpack
15
- 'application/x-msgpack'
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
- def request_body_includes(request, protocol, key, val)
22
- body = if protocol == :msgpack
23
- MessagePack.unpack(request.body)
24
- else
25
- JSON.parse(request.body)
26
- end
27
- body[key.to_s].to_s == val.to_s
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
- def serialize(object, protocol)
31
- if protocol == :msgpack
32
- MessagePack.pack(token_response)
33
- else
34
- JSON.dump(token_response)
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
- describe "#request_token" do
39
- let(:ttl) { 30 * 60 }
40
- let(:capability) { { :foo => ['publish'] } }
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
- it 'returns the requested token' do
43
- actual_token = auth.request_token(
44
- ttl: ttl,
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
- expect(actual_token.id).to match(/^#{app_id}\.[\w-]+$/)
49
- expect(actual_token.key_id).to match(/^#{key_id}$/)
50
- expect(actual_token.issued_at).to be_within(2).of(Time.now)
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
- before { auth.request_token options }
60
+ it 'returns the requested token' do
61
+ actual_token = auth.request_token(
62
+ ttl: ttl,
63
+ capability: capability
64
+ )
72
65
 
73
- it 'overrides default' do
74
- expect(request_token_stub).to have_been_requested
75
- end
76
- end
77
- end
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
- context 'with :key_id & :key_secret options', webmock: true do
80
- let(:key_id) { SecureRandom.hex }
81
- let(:key_secret) { SecureRandom.hex }
82
- let(:nonce) { SecureRandom.hex }
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, 'mac', mac)
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
- let!(:token) { auth.request_token(token_options) }
89
+ before { auth.request_token options }
101
90
 
102
- specify 'key_id is used in request and signing uses key_secret' do
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
- context 'with :query_time option' do
108
- let(:options) { { query_time: true } }
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
- it 'queries the server for the time' do
111
- expect(client).to receive(:time).and_call_original
112
- auth.request_token(options)
113
- end
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
- context 'without :query_time option' do
117
- let(:options) { { query_time: false } }
118
+ let!(:token) { auth.request_token(token_options) }
118
119
 
119
- it 'queries the server for the time' do
120
- expect(client).to_not receive(:time)
121
- auth.request_token(options)
122
- end
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
- context 'with :auth_url option', webmock: true do
126
- let(:auth_url) { 'https://www.fictitious.com/get_token' }
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
- let!(:auth_url_request_stub) do
142
- stub = stub_request(auth_method, auth_url)
143
- stub.with(:query => query_params) unless query_params.nil?
144
- stub.with(:headers => headers) unless headers.nil?
145
- stub.to_return(
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 => token_request.to_json,
148
- :headers => { 'Content-Type' => 'application/json' }
176
+ :body => serialize(token_response, protocol),
177
+ :headers => { 'Content-Type' => content_type }
149
178
  )
150
- end
179
+ end
151
180
 
152
- let!(:request_token_stub) do
153
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
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
- context 'valid' do
164
- before { auth.request_token options }
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
- context 'and default options' do
167
- it 'requests a token from :auth_url' do
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
- context 'with params' do
174
- let(:query_params) { { 'key' => SecureRandom.hex } }
175
- it 'requests a token from :auth_url' do
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
- context 'with headers' do
182
- let(:headers) { { 'key' => SecureRandom.hex } }
183
- it 'requests a token from :auth_url' do
184
- expect(request_token_stub).to have_been_requested
185
- expect(auth_url_request_stub).to have_been_requested
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
- context 'with POST' do
190
- let(:auth_method) { :post }
191
- it 'requests a token from :auth_url' do
192
- expect(request_token_stub).to have_been_requested
193
- expect(auth_url_request_stub).to have_been_requested
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
- context 'when response is invalid' do
199
- context '500' do
200
- let!(:auth_url_request_stub) do
201
- stub_request(auth_method, auth_url).to_return(:status => 500)
202
- end
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
- it 'raises ServerError' do
205
- expect { auth.request_token options }.to raise_error(Ably::Exceptions::ServerError)
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
- context 'XML' do
210
- let!(:auth_url_request_stub) do
211
- stub_request(auth_method, auth_url).
212
- to_return(:status => 201, :body => '<xml></xml>', :headers => { 'Content-Type' => 'application/xml' })
213
- end
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
- it 'raises InvalidResponseBody' do
216
- expect { auth.request_token options }.to raise_error(Ably::Exceptions::InvalidResponseBody)
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
- context 'with auth_block' do
223
- let(:client_id) { SecureRandom.hex }
224
- let(:options) { { client_id: client_id } }
225
- let!(:token) do
226
- auth.request_token(options) do |block_options|
227
- @block_called = true
228
- @block_options = block_options
229
- auth.create_token_request(client_id: client_id)
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
- it 'calls the block' do
234
- expect(@block_called).to eql(true)
235
- expect(@block_options).to include(options)
236
- end
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
- it 'uses the token request when requesting a new token' do
239
- expect(token.client_id).to eql(client_id)
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
- describe '#authorise' do
245
- context 'with no previous authorisation' do
246
- let(:request_options) do
247
- { auth_url: 'http://somewhere.com/' }
248
- end
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
- it 'has no current_token' do
251
- expect(auth.current_token).to be_nil
252
- end
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
- it 'passes all options to request_token' do
255
- expect(auth).to receive(:request_token).with(request_options)
256
- auth.authorise request_options
257
- end
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
- it 'returns a valid token' do
260
- expect(auth.authorise).to be_a(Ably::Models::Token)
261
- end
278
+ it 'returns a valid token' do
279
+ expect(auth.authorise).to be_a(Ably::Models::Token)
280
+ end
262
281
 
263
- it 'issues a new token if option :force => true' do
264
- expect { auth.authorise(force: true) }.to change { auth.current_token }
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
- context 'with previous authorisation' do
269
- before do
270
- auth.authorise
271
- expect(auth.current_token).to_not be_expired
272
- end
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
- it 'does not request a token if token is not expired' do
275
- expect(auth).to_not receive(:request_token)
276
- auth.authorise
277
- end
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
- it 'requests a new token if token is expired' do
280
- allow(auth.current_token).to receive(:expired?).and_return(true)
281
- expect(auth).to receive(:request_token)
282
- expect { auth.authorise }.to change { auth.current_token }
283
- end
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
- it 'issues a new token if option :force => true' do
286
- expect { auth.authorise(force: true) }.to change { auth.current_token }
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
- describe '#create_token_request' do
292
- let(:ttl) { 60 * 60 }
293
- let(:capability) { { :foo => ["publish"] } }
294
- let(:options) { Hash.new }
295
- subject { auth.create_token_request(options) }
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
- it 'uses the key ID from the client' do
298
- expect(subject[:id]).to eql(key_id)
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 'uses the default TTL' do
302
- expect(subject[:ttl]).to eql(Ably::Models::Token::DEFAULTS[:ttl])
325
+ it 'calls the block' do
326
+ expect(@block_called).to eql(1)
303
327
  end
304
328
 
305
- it 'uses the default capability' do
306
- expect(subject[:capability]).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
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
- it 'has a unique nonce' do
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 'has a nonce of at least 16 characters' do
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
- %w(ttl capability nonce timestamp client_id).each do |attribute|
319
- context "with option :#{attribute}" do
320
- let(:option_value) { SecureRandom.random_number(1_000_000_000) }
321
- before do
322
- options[attribute.to_sym] = option_value
323
- end
324
- it "overrides default" do
325
- expect(subject[attribute.to_sym]).to eql(option_value)
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
- context 'invalid attributes' do
331
- let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
332
- specify 'are ignored' do
333
- expect(subject.keys).to_not include(:is_not_used_by_token_request)
334
- expect(subject.keys).to include(:nonce)
335
- expect(subject[:nonce]).to eql('valid')
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
- context 'missing key ID and/or secret' do
340
- let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
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
- it 'should raise an exception if key secret is missing' do
343
- expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
344
- end
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
- it 'should raise an exception if key id is missing' do
347
- expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
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
- context 'with :query_time option' do
352
- let(:time) { Time.now - 30 }
353
- let(:options) { { query_time: true } }
414
+ context 'with :query_time option' do
415
+ let(:time) { Time.now - 30 }
416
+ let(:options) { { query_time: true } }
354
417
 
355
- it 'queries the server for the time' do
356
- expect(client).to receive(:time).and_return(time)
357
- expect(subject[:timestamp]).to eql(time.to_i)
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
- context 'with :timestamp option' do
362
- let(:token_request_time) { Time.now + 5 }
363
- let(:options) { { timestamp: token_request_time } }
424
+ context 'with :timestamp option' do
425
+ let(:token_request_time) { Time.now + 5 }
426
+ let(:options) { { timestamp: token_request_time } }
364
427
 
365
- it 'uses the provided timestamp' do
366
- expect(subject[:timestamp]).to eql(token_request_time.to_i)
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
- context 'signing' do
371
- let(:options) do
372
- {
373
- id: SecureRandom.hex,
374
- ttl: SecureRandom.hex,
375
- capability: SecureRandom.hex,
376
- client_id: SecureRandom.hex,
377
- timestamp: SecureRandom.random_number(1_000_000_000),
378
- nonce: SecureRandom.hex
379
- }
380
- end
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
- it 'generates a valid HMAC' do
383
- hmac = hmac_for(options, key_secret)
384
- expect(subject[:mac]).to eql(hmac)
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
- context 'client with token authentication' do
390
- let(:capability) { { :foo => ["publish"] } }
452
+ context 'using token authentication' do
453
+ let(:capability) { { :foo => ["publish"] } }
391
454
 
392
- describe "with token_id argument" do
393
- let(:ttl) { 60 * 60 }
394
- let(:token) do
395
- auth.request_token(
396
- ttl: ttl,
397
- capability: capability
398
- )
399
- end
400
- let(:token_id) { token.id }
401
- let(:token_auth_client) do
402
- Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
403
- end
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
- it 'authenticates successfully' do
406
- expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
407
- end
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
- it 'disallows publishing on unspecified capability channels' do
410
- expect { token_auth_client.channel('bar').publish('event', 'data') }.to raise_error do |error|
411
- expect(error).to be_a(Ably::Exceptions::InvalidToken)
412
- expect(error.status).to eql(401)
413
- expect(error.code).to eql(40160)
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
- it 'fails if timestamp is invalid' do
418
- expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
419
- expect(error).to be_a(Ably::Exceptions::InvalidToken)
420
- expect(error.status).to eql(401)
421
- expect(error.code).to eql(40101)
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
- describe 'implicit through client id' do
427
- let(:client_id) { '999' }
428
- let(:client) do
429
- Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment, protocol: protocol)
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(:token_id) { 'unique-token-id' }
432
- let(:token_response) do
433
- {
434
- access_token: {
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
- context 'stubbed', webmock: true do
441
- let!(:request_token_stub) do
442
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
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
- context 'will create a token' do
458
- let(:token) { client.auth.current_token }
524
+ describe 'a token is created' do
525
+ let(:token) { client.auth.current_token }
459
526
 
460
- it 'before a request is made' do
461
- expect(token).to be_nil
462
- end
527
+ it 'before a request is made' do
528
+ expect(token).to be_nil
529
+ end
463
530
 
464
- it 'when a message is published' do
465
- expect(client.channel('foo').publish('event', 'data')).to be_truthy
466
- end
531
+ it 'when a message is published' do
532
+ expect(client.channel('foo').publish('event', 'data')).to be_truthy
533
+ end
467
534
 
468
- it 'with capability and TTL defaults' do
469
- client.channel('foo').publish('event', 'data')
535
+ it 'with capability and TTL defaults' do
536
+ client.channel('foo').publish('event', 'data')
470
537
 
471
- expect(token).to be_a(Ably::Models::Token)
472
- capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]
473
- capability = Hash[capability_with_str_key.keys.map(&:to_sym).zip(capability_with_str_key.values)]
474
- expect(token.capability).to eq(capability)
475
- expect(token.expires_at.to_i).to be_within(2).of(Time.now.to_i + Ably::Models::Token::DEFAULTS[:ttl])
476
- expect(token.client_id).to eq(client_id)
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
- def hmac_for(token_request, secret)
485
- text = token_request.values_at(
486
- :id,
487
- :ttl,
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
- encode64(
495
- Digest::HMAC.digest(text, key_secret, Digest::SHA256)
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