ably-rest 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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