content_gateway 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/COPYING +24 -0
- data/Changelog +42 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +61 -0
- data/README.md +134 -0
- data/Rakefile +10 -0
- data/content-gateway.gemspec +26 -0
- data/lib/content_gateway/cache.rb +59 -0
- data/lib/content_gateway/exceptions.rb +79 -0
- data/lib/content_gateway/gateway.rb +153 -0
- data/lib/content_gateway/request.rb +48 -0
- data/lib/content_gateway/version.rb +3 -0
- data/lib/content_gateway.rb +23 -0
- data/spec/integration/content_gateway/gateway_spec.rb +410 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/unit/content_gateway/cache_spec.rb +134 -0
- data/spec/unit/content_gateway/gateway_spec.rb +121 -0
- data/spec/unit/content_gateway/request_spec.rb +105 -0
- metadata +163 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "logger"
|
3
|
+
require "timeout"
|
4
|
+
require "benchmark"
|
5
|
+
require "json"
|
6
|
+
require "rest-client"
|
7
|
+
require "active_support/cache"
|
8
|
+
require "active_support/core_ext/object/blank"
|
9
|
+
require "active_support/core_ext/date_time/calculations"
|
10
|
+
require "active_support/core_ext/hash/indifferent_access"
|
11
|
+
|
12
|
+
module ContentGateway
|
13
|
+
extend self
|
14
|
+
|
15
|
+
def logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "content_gateway/version"
|
20
|
+
require "content_gateway/exceptions"
|
21
|
+
require "content_gateway/cache"
|
22
|
+
require "content_gateway/request"
|
23
|
+
require "content_gateway/gateway"
|
@@ -0,0 +1,410 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ContentGateway::Gateway do
|
4
|
+
let! :url_generator do
|
5
|
+
url_generator = double('url_generator')
|
6
|
+
allow(url_generator).to receive(:generate).with(resource_path, {}).and_return("http://api.com/servico")
|
7
|
+
url_generator
|
8
|
+
end
|
9
|
+
|
10
|
+
let! :config do
|
11
|
+
OpenStruct.new(
|
12
|
+
cache: ActiveSupport::Cache::NullStore.new,
|
13
|
+
cache_expires_in: 15.minutes,
|
14
|
+
cache_stale_expires_in: 1.hour,
|
15
|
+
proxy: "proxy"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
let :gateway do
|
20
|
+
ContentGateway::Gateway.new "API XPTO", config, url_generator, headers: headers
|
21
|
+
end
|
22
|
+
|
23
|
+
let :params do
|
24
|
+
{ "a|b" => 1, name: "a|b|c" }
|
25
|
+
end
|
26
|
+
|
27
|
+
let :headers do
|
28
|
+
{ key: 'value' }
|
29
|
+
end
|
30
|
+
|
31
|
+
let :resource_path do
|
32
|
+
"anything"
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:timeout) { 0.1 }
|
36
|
+
|
37
|
+
let :cached_response do
|
38
|
+
response = "cached response"
|
39
|
+
response.instance_eval do
|
40
|
+
def code
|
41
|
+
200
|
42
|
+
end
|
43
|
+
end
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
before do
|
48
|
+
config.cache.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".new" do
|
52
|
+
it "default_params should be optional" do
|
53
|
+
expect(ContentGateway::Gateway.new("API XPTO", config, url_generator)).to be_kind_of(ContentGateway::Gateway)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#get" do
|
58
|
+
let :resource_url do
|
59
|
+
url_generator.generate(resource_path, {})
|
60
|
+
end
|
61
|
+
|
62
|
+
let :stale_cache_key do
|
63
|
+
"stale:#{resource_url}"
|
64
|
+
end
|
65
|
+
|
66
|
+
let :default_expires_in do
|
67
|
+
config.cache_expires_in
|
68
|
+
end
|
69
|
+
|
70
|
+
let :default_stale_expires_in do
|
71
|
+
config.cache_stale_expires_in
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with all request params" do
|
75
|
+
before do
|
76
|
+
stub_request(method: :get, proxy: config.proxy, url: resource_url, headers: headers)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should do request with http get" do
|
80
|
+
gateway.get resource_path
|
81
|
+
end
|
82
|
+
|
83
|
+
context "with cache" do
|
84
|
+
it "should cache responses" do
|
85
|
+
cache_store = double("cache_store")
|
86
|
+
expect(cache_store).to receive(:fetch).with(resource_url, expires_in: default_expires_in)
|
87
|
+
config.cache = cache_store
|
88
|
+
|
89
|
+
gateway.get resource_path
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should keep stale cache" do
|
93
|
+
stub_request(url: resource_url, proxy: config.proxy, headers: headers) { cached_response }
|
94
|
+
|
95
|
+
cache_store = double("cache_store")
|
96
|
+
expect(cache_store).to receive(:fetch).with(resource_url, expires_in: default_expires_in).and_yield
|
97
|
+
expect(cache_store).to receive(:write).with(stale_cache_key, cached_response, expires_in: default_stale_expires_in)
|
98
|
+
config.cache = cache_store
|
99
|
+
|
100
|
+
gateway.get resource_path
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "timeout control" do
|
104
|
+
before do
|
105
|
+
stub_request(method: :get, url: resource_url, proxy: config.proxy, headers: headers) {
|
106
|
+
sleep(0.3)
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should accept 'timeout' to overwrite the default value" do
|
111
|
+
expect(Timeout).to receive(:timeout).with(timeout)
|
112
|
+
gateway.get resource_path, timeout: timeout
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should block requests that expire the configured timeout" do
|
116
|
+
expect { gateway.get resource_path, timeout: timeout }.to raise_error ContentGateway::TimeoutError
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should block cache requests that expire the configured timeout" do
|
120
|
+
allow(config.cache).to receive(:fetch) { sleep(1) }
|
121
|
+
expect { gateway.get resource_path, timeout: timeout }.to raise_error ContentGateway::TimeoutError
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "with stale cache" do
|
126
|
+
context "on timeout" do
|
127
|
+
before do
|
128
|
+
cache_store = double("cache_store")
|
129
|
+
allow(cache_store).to receive(:fetch).with(resource_url, expires_in: default_expires_in).and_raise(Timeout::Error)
|
130
|
+
allow(cache_store).to receive(:read).with(stale_cache_key).and_return(cached_response)
|
131
|
+
config.cache = cache_store
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should serve stale" do
|
135
|
+
expect(gateway.get(resource_path, timeout: timeout)).to eql "cached response"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "on server error" do
|
140
|
+
before do
|
141
|
+
stub_request_with_error({method: :get, url: resource_url, proxy: config.proxy, headers: headers}, RestClient::InternalServerError.new(nil, 500))
|
142
|
+
|
143
|
+
cache_store = double("cache_store")
|
144
|
+
allow(cache_store).to receive(:fetch).with(resource_url, expires_in: default_expires_in).and_yield
|
145
|
+
allow(cache_store).to receive(:read).with(stale_cache_key).and_return(cached_response)
|
146
|
+
config.cache = cache_store
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should serve stale" do
|
150
|
+
expect(gateway.get(resource_path)).to eql "cached response"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context "with skip_cache parameter" do
|
157
|
+
it "shouldn't cache requests" do
|
158
|
+
cache_store = double("cache_store")
|
159
|
+
expect(cache_store).not_to receive(:fetch).with(resource_url, expires_in: default_expires_in)
|
160
|
+
config.cache = cache_store
|
161
|
+
|
162
|
+
gateway.get resource_path, skip_cache: true
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "timeout control" do
|
166
|
+
let(:timeout) { 0.1 }
|
167
|
+
|
168
|
+
before do
|
169
|
+
stub_request(method: :get, url: resource_url, proxy: config.proxy, headers: headers) {
|
170
|
+
sleep(0.3)
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should ignore 'timeout' parameter" do
|
175
|
+
expect(Timeout).not_to receive(:timeout).with(timeout)
|
176
|
+
gateway.get resource_path, skip_cache: true, timeout: timeout
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "on server error" do
|
181
|
+
before do
|
182
|
+
stub_request_with_error({method: :get, url: resource_url, proxy: config.proxy, headers: headers}, RestClient::InternalServerError.new(nil, 500))
|
183
|
+
|
184
|
+
cache_store = double("cache_store")
|
185
|
+
expect(cache_store).not_to receive(:fetch).with(resource_url, expires_in: default_expires_in).and_yield
|
186
|
+
config.cache = cache_store
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should ignore cache" do
|
190
|
+
expect { gateway.get(resource_path, skip_cache: true) }.to raise_error ContentGateway::ServerError
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should raise NotFound exception on 404 error" do
|
196
|
+
stub_request_with_error({ method: :get, url: resource_url, proxy: config.proxy, headers: headers }, RestClient::ResourceNotFound.new)
|
197
|
+
expect { gateway.get resource_path }.to raise_error ContentGateway::ResourceNotFound
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should raise Conflict exception on 409 error" do
|
201
|
+
stub_request_with_error({ method: :get, url: resource_url, proxy: config.proxy, headers: headers }, RestClient::Conflict.new)
|
202
|
+
expect { gateway.get resource_path }.to raise_error ContentGateway::ConflictError
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should raise ServerError exception on 500 error" do
|
206
|
+
stub_request_with_error({ method: :get, url: resource_url, proxy: config.proxy, headers: headers }, RestClient::Exception.new(nil, 500))
|
207
|
+
expect { gateway.get resource_path }.to raise_error ContentGateway::ServerError
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should raise ConnectionFailure exception on other errors" do
|
211
|
+
stub_request_with_error({ method: :get, url: resource_url, proxy: config.proxy, headers: headers }, SocketError.new)
|
212
|
+
expect { gateway.get resource_path }.to raise_error ContentGateway::ConnectionFailure
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should accept a 'expires_in' parameter to overwrite the default value" do
|
216
|
+
expires_in = 3.minutes
|
217
|
+
cache_store = double("cache_store")
|
218
|
+
expect(cache_store).to receive(:fetch).with(resource_url, expires_in: expires_in)
|
219
|
+
config.cache = cache_store
|
220
|
+
gateway.get resource_path, expires_in: expires_in
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should accept a 'stale_expires_in' parameter to overwrite the default value" do
|
224
|
+
stub_request(url: resource_url, proxy: config.proxy, headers: headers) { cached_response }
|
225
|
+
|
226
|
+
stale_expires_in = 5.minutes
|
227
|
+
cache_store = double("cache_store")
|
228
|
+
allow(cache_store).to receive(:fetch).with(resource_url, expires_in: default_expires_in).and_yield
|
229
|
+
expect(cache_store).to receive(:write).with(stale_cache_key, cached_response, expires_in: stale_expires_in)
|
230
|
+
config.cache = cache_store
|
231
|
+
|
232
|
+
gateway.get resource_path, stale_expires_in: stale_expires_in
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context "without proxy" do
|
237
|
+
before do
|
238
|
+
config.proxy = nil
|
239
|
+
stub_request(method: :get, url: resource_url, headers: headers)
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should do request with http get" do
|
243
|
+
gateway.get resource_path
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "overwriting headers" do
|
248
|
+
let :novos_headers do
|
249
|
+
{ key2: 'value2' }
|
250
|
+
end
|
251
|
+
|
252
|
+
before do
|
253
|
+
stub_request(method: :get, proxy: config.proxy, url: resource_url, headers: novos_headers)
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should do request with http get" do
|
257
|
+
gateway.get resource_path, headers: novos_headers
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "#get_json" do
|
263
|
+
it "should convert the get result to JSON" do
|
264
|
+
expect(gateway).to receive(:get).with(resource_path, params).and_return({ "a" => 1 }.to_json)
|
265
|
+
expect(gateway.get_json(resource_path, params)).to eql("a" => 1)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "#post_json" do
|
270
|
+
it "should convert the post result to JSON" do
|
271
|
+
expect(gateway).to receive(:post).with(resource_path, params).and_return({ "a" => 1 }.to_json)
|
272
|
+
expect(gateway.post_json(resource_path, params)).to eql("a" => 1)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe "#put_json" do
|
277
|
+
it "should convert the put result to JSON" do
|
278
|
+
expect(gateway).to receive(:put).with(resource_path, params).and_return({ "a" => 1 }.to_json)
|
279
|
+
expect(gateway.put_json(resource_path, params)).to eql("a" => 1)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#post" do
|
284
|
+
let :resource_url do
|
285
|
+
url_generator.generate(resource_path, {})
|
286
|
+
end
|
287
|
+
|
288
|
+
let :payload do
|
289
|
+
{ param: "value" }
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should do request with http post" do
|
293
|
+
stub_request(method: :post, url: resource_url, proxy: config.proxy, payload: payload)
|
294
|
+
gateway.post resource_path, payload: payload
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should raise NotFound exception on 404 error" do
|
298
|
+
stub_request_with_error({ method: :post, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::ResourceNotFound.new)
|
299
|
+
expect { gateway.post resource_path, payload: payload }.to raise_error ContentGateway::ResourceNotFound
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should raise UnprocessableEntity exception on 401 error" do
|
303
|
+
stub_request_with_error({ method: :post, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::Unauthorized.new)
|
304
|
+
expect { gateway.post resource_path, payload: payload }.to raise_error(ContentGateway::UnauthorizedError)
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should raise Forbidden exception on 403 error" do
|
308
|
+
stub_request_with_error({ method: :post, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::Forbidden.new)
|
309
|
+
expect { gateway.post resource_path, payload: payload }.to raise_error(ContentGateway::Forbidden)
|
310
|
+
end
|
311
|
+
|
312
|
+
it "should raise ConnectionFailure exception on 500 error" do
|
313
|
+
stub_request_with_error({ method: :post, url: resource_url, proxy: config.proxy, payload: payload }, SocketError.new)
|
314
|
+
expect { gateway.post resource_path, payload: payload }.to raise_error ContentGateway::ConnectionFailure
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "#delete" do
|
319
|
+
let :resource_url do
|
320
|
+
url_generator.generate(resource_path, {})
|
321
|
+
end
|
322
|
+
|
323
|
+
let :payload do
|
324
|
+
{ param: "value" }
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should do request with http post" do
|
328
|
+
stub_request(method: :delete, url: resource_url, proxy: config.proxy, payload: payload)
|
329
|
+
gateway.delete resource_path, payload: payload
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should raise NotFound exception on 404 error" do
|
333
|
+
stub_request_with_error({ method: :delete, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::ResourceNotFound.new)
|
334
|
+
expect { gateway.delete resource_path, payload: payload }.to raise_error ContentGateway::ResourceNotFound
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should raise UnprocessableEntity exception on 401 error" do
|
338
|
+
stub_request_with_error({ method: :delete, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::Unauthorized.new)
|
339
|
+
expect { gateway.delete resource_path, payload: payload }.to raise_error(ContentGateway::UnauthorizedError)
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should raise Forbidden exception on 403 error" do
|
343
|
+
stub_request_with_error({ method: :delete, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::Forbidden.new)
|
344
|
+
expect { gateway.delete resource_path, payload: payload }.to raise_error(ContentGateway::Forbidden)
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should raise ConnectionFailure exception on 500 error" do
|
348
|
+
stub_request_with_error({ method: :delete, url: resource_url, proxy: config.proxy, payload: payload }, SocketError.new)
|
349
|
+
expect { gateway.delete resource_path, payload: payload }.to raise_error ContentGateway::ConnectionFailure
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe "#put" do
|
354
|
+
let :resource_url do
|
355
|
+
gateway.generate_url(resource_path)
|
356
|
+
end
|
357
|
+
|
358
|
+
let :payload do
|
359
|
+
{ param: "value" }
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should do request with http put" do
|
363
|
+
stub_request(method: :put, url: resource_url, proxy: config.proxy, payload: payload)
|
364
|
+
gateway.put resource_path, payload: payload
|
365
|
+
end
|
366
|
+
|
367
|
+
it "should raise NotFound exception on 404 error" do
|
368
|
+
stub_request_with_error({ method: :put, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::ResourceNotFound.new)
|
369
|
+
expect { gateway.put resource_path, payload: payload }.to raise_error ContentGateway::ResourceNotFound
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should raise UnprocessableEntity exception on 422 error" do
|
373
|
+
stub_request_with_error({ method: :put, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::UnprocessableEntity)
|
374
|
+
expect { gateway.put resource_path, payload: payload }.to raise_error ContentGateway::ValidationError
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should raise Forbidden exception on 403 error" do
|
378
|
+
stub_request_with_error({ method: :put, url: resource_url, proxy: config.proxy, payload: payload }, RestClient::Forbidden.new)
|
379
|
+
expect { gateway.put resource_path, payload: payload }.to raise_error(ContentGateway::Forbidden)
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should raise ConnectionFailure exception on 500 error" do
|
383
|
+
stub_request_with_error({ method: :put, url: resource_url, proxy: config.proxy, payload: payload }, SocketError.new)
|
384
|
+
expect { gateway.put resource_path, payload: payload }.to raise_error ContentGateway::ConnectionFailure
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
|
390
|
+
def stub_request(opts, payload = {}, &block)
|
391
|
+
opts = { method: :get, proxy: :none }.merge(opts)
|
392
|
+
request = RestClient::Request.new(opts)
|
393
|
+
allow(RestClient::Request).to receive(:new).with(opts).and_return(request)
|
394
|
+
|
395
|
+
allow(request).to receive(:execute) do
|
396
|
+
block.call if block_given?
|
397
|
+
end
|
398
|
+
|
399
|
+
request
|
400
|
+
end
|
401
|
+
|
402
|
+
def stub_request_with_error(opts, exc)
|
403
|
+
opts = { method: :get, proxy: :none }.merge(opts)
|
404
|
+
|
405
|
+
request = RestClient::Request.new(opts)
|
406
|
+
allow(RestClient::Request).to receive(:new).with(opts).and_return(request)
|
407
|
+
|
408
|
+
allow(request).to receive(:execute).and_raise(exc)
|
409
|
+
end
|
410
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
4
|
+
|
5
|
+
require 'byebug'
|
6
|
+
require 'rspec'
|
7
|
+
require 'content_gateway'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'simplecov'
|
11
|
+
SimpleCov.start do
|
12
|
+
add_filter '/spec/'
|
13
|
+
end
|
14
|
+
SimpleCov.coverage_dir 'coverage/rspec'
|
15
|
+
rescue LoadError
|
16
|
+
# ignore simplecov in ruby < 1.9
|
17
|
+
end
|
18
|
+
|
19
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
20
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
21
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
22
|
+
# loaded once.
|
23
|
+
#
|
24
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
25
|
+
|
26
|
+
RSpec.configure do |config|
|
27
|
+
config.run_all_when_everything_filtered = true
|
28
|
+
config.filter_run :focus
|
29
|
+
|
30
|
+
# Run specs in random order to surface order dependencies. If you find an
|
31
|
+
# order dependency and want to debug it, you can fix the order by providing
|
32
|
+
# the seed, which is printed after each run.
|
33
|
+
# --seed 1234
|
34
|
+
config.order = 'random'
|
35
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ContentGateway::Cache do
|
4
|
+
subject do
|
5
|
+
ContentGateway::Cache.new(config, url, method, params)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:config) { OpenStruct.new(cache: cache_store) }
|
9
|
+
|
10
|
+
let(:cache_store) { double("cache store", write: nil) }
|
11
|
+
|
12
|
+
let(:url) { "/url" }
|
13
|
+
|
14
|
+
let(:method) { :get }
|
15
|
+
|
16
|
+
let(:params) { {} }
|
17
|
+
|
18
|
+
describe "#use?" do
|
19
|
+
context "when skip_cache is true" do
|
20
|
+
let(:params) { { skip_cache: true } }
|
21
|
+
|
22
|
+
it "shouldn't use cache" do
|
23
|
+
expect(subject.use?).to eql false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when method isn't get or head" do
|
28
|
+
let(:method) { :post }
|
29
|
+
|
30
|
+
it "shouldn't use cache" do
|
31
|
+
expect(subject.use?).to eql false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when method is get" do
|
36
|
+
let(:method) { :get }
|
37
|
+
|
38
|
+
it "should use cache" do
|
39
|
+
expect(subject.use?).to eql true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when method is head" do
|
44
|
+
let(:method) { :head }
|
45
|
+
|
46
|
+
it "should use cache" do
|
47
|
+
expect(subject.use?).to eql true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#fetch" do
|
53
|
+
let(:request) { double("request", execute: "data") }
|
54
|
+
|
55
|
+
context "when cache hits" do
|
56
|
+
before do
|
57
|
+
expect(Timeout).to receive(:timeout) do |timeout, &arg|
|
58
|
+
arg.call
|
59
|
+
end
|
60
|
+
|
61
|
+
expect(cache_store).to receive(:fetch).with(url, expires_in: 100).and_return("cached data")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return the cached data" do
|
65
|
+
expect(subject.fetch(request, expires_in: 100)).to eql "cached data"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when cache misses" do
|
70
|
+
context "and request succeeds" do
|
71
|
+
before do
|
72
|
+
expect(Timeout).to receive(:timeout) do |timeout, &arg|
|
73
|
+
arg.call
|
74
|
+
end
|
75
|
+
|
76
|
+
expect(cache_store).to receive(:fetch) do |url, params, &arg|
|
77
|
+
arg.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should set status to 'MISS'" do
|
82
|
+
subject.fetch(request)
|
83
|
+
|
84
|
+
expect(subject.status).to eql "MISS"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return the request data" do
|
88
|
+
expect(subject.fetch(request)).to eql "data"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should write the request data to stale cache" do
|
92
|
+
expect(cache_store).to receive(:write).with("stale:/url", "data", expires_in: 15)
|
93
|
+
|
94
|
+
subject.fetch(request, stale_expires_in: 15)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#serve_stale" do
|
101
|
+
before do
|
102
|
+
expect(cache_store).to receive(:read).with("stale:/url").and_return(return_value)
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when data are successfully read from stale cache" do
|
106
|
+
let(:return_value) { "stale cache data" }
|
107
|
+
|
108
|
+
it "should return the stale data" do
|
109
|
+
expect(subject.serve_stale).to eql "stale cache data"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should set status to 'STALE'" do
|
113
|
+
subject.serve_stale
|
114
|
+
expect(subject.status).to eql "STALE"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "when data can't be read from stale cache" do
|
119
|
+
let(:return_value) { nil }
|
120
|
+
|
121
|
+
it "should raise ContentGateway::StaleCacheNotAvailableError" do
|
122
|
+
expect { subject.serve_stale }.to raise_error ContentGateway::StaleCacheNotAvailableError
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#stale_key" do
|
128
|
+
let(:url) { "http://example.com" }
|
129
|
+
|
130
|
+
it "should return the stale cache key" do
|
131
|
+
expect(subject.stale_key).to eql "stale:http://example.com"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|