content_gateway 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|