ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. 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