garage_client 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +40 -0
  5. data/Gemfile +8 -0
  6. data/Guardfile +7 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +196 -0
  9. data/Rakefile +8 -0
  10. data/garage_client.gemspec +36 -0
  11. data/gemfiles/Gemfile.faraday-0.8.x +4 -0
  12. data/lib/garage_client.rb +33 -0
  13. data/lib/garage_client/cachers/base.rb +44 -0
  14. data/lib/garage_client/client.rb +93 -0
  15. data/lib/garage_client/configuration.rb +51 -0
  16. data/lib/garage_client/error.rb +37 -0
  17. data/lib/garage_client/request.rb +38 -0
  18. data/lib/garage_client/request/json_encoded.rb +59 -0
  19. data/lib/garage_client/resource.rb +63 -0
  20. data/lib/garage_client/response.rb +123 -0
  21. data/lib/garage_client/response/cacheable.rb +27 -0
  22. data/lib/garage_client/response/raise_http_exception.rb +34 -0
  23. data/lib/garage_client/version.rb +3 -0
  24. data/spec/features/configuration_spec.rb +46 -0
  25. data/spec/fixtures/example.yaml +56 -0
  26. data/spec/fixtures/examples.yaml +60 -0
  27. data/spec/fixtures/examples_dictionary.yaml +60 -0
  28. data/spec/fixtures/examples_without_pagination.yaml +58 -0
  29. data/spec/garage_client/cacher_spec.rb +55 -0
  30. data/spec/garage_client/client_spec.rb +228 -0
  31. data/spec/garage_client/configuration_spec.rb +106 -0
  32. data/spec/garage_client/error_spec.rb +37 -0
  33. data/spec/garage_client/request/json_encoded_spec.rb +66 -0
  34. data/spec/garage_client/resource_spec.rb +102 -0
  35. data/spec/garage_client/response_spec.rb +450 -0
  36. data/spec/garage_client_spec.rb +48 -0
  37. data/spec/spec_helper.rb +56 -0
  38. metadata +275 -0
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ describe GarageClient::Configuration do
4
+ let(:configuration) do
5
+ described_class.new
6
+ end
7
+
8
+ describe "#adapter" do
9
+ context "in default configuration" do
10
+ it "returns :net_http" do
11
+ configuration.adapter.should == :net_http
12
+ end
13
+ end
14
+
15
+ context "after configured" do
16
+ before do
17
+ configuration.adapter = :test
18
+ end
19
+
20
+ it "returns configured value" do
21
+ configuration.adapter.should == :test
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#endpoint" do
27
+ context "not configured" do
28
+ it "raises RuntimeError" do
29
+ expect { configuration.endpoint }.to raise_error(RuntimeError, /missing endpoint/)
30
+ end
31
+ end
32
+
33
+ context "after configured" do
34
+ before do
35
+ configuration.endpoint = "http://example.com"
36
+ end
37
+
38
+ it "returns configured value" do
39
+ configuration.endpoint.should == "http://example.com"
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#headers" do
45
+ context "in default configuration" do
46
+ it "returns default headers as Hash" do
47
+ configuration.headers.should == {
48
+ "Accept" => "application/json",
49
+ "User-Agent" => "garage_client #{GarageClient::VERSION}",
50
+ }
51
+ end
52
+ end
53
+
54
+ context "after configured" do
55
+ before do
56
+ configuration.headers = { "HTTP_ACCEPT" => "application/json" }
57
+ end
58
+
59
+ it "returns configured value" do
60
+ configuration.headers.should == { "HTTP_ACCEPT" => "application/json" }
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#default_headers" do
66
+ it "returns configuration.headers" do
67
+ configuration.default_headers.should == configuration.headers
68
+ end
69
+ end
70
+
71
+ describe "#path_prefix" do
72
+ context "in default configuration" do
73
+ it "returns /v1" do
74
+ configuration.path_prefix.should == "/v1"
75
+ end
76
+ end
77
+
78
+ context "after configured" do
79
+ before do
80
+ configuration.path_prefix = "/v2"
81
+ end
82
+
83
+ it "returns configured value" do
84
+ configuration.path_prefix.should == "/v2"
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#verbose" do
90
+ context "in default configuration" do
91
+ it "returns false" do
92
+ configuration.verbose.should == false
93
+ end
94
+ end
95
+
96
+ context "after configured" do
97
+ before do
98
+ configuration.verbose = nil
99
+ end
100
+
101
+ it "returns configured value" do
102
+ configuration.verbose.should == nil
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe GarageClient::Error do
4
+ context 'without argument' do
5
+ it 'raises GarageClient::Error' do
6
+ expect { raise GarageClient::Error }.to raise_error(GarageClient::Error, /GarageClient::Error/)
7
+ end
8
+ end
9
+
10
+ context 'with string' do
11
+ let(:message) do
12
+ 'error_message'
13
+ end
14
+
15
+ it 'raises GarageClient::Error with error message' do
16
+ expect { raise GarageClient::Error, message }.to raise_error(GarageClient::Error, message)
17
+ end
18
+ end
19
+
20
+ context 'with Faraday::Response' do
21
+ let(:client) do
22
+ Faraday.new do |builder|
23
+ builder.adapter :test, Faraday::Adapter::Test::Stubs.new do |stub|
24
+ stub.get('/example') { [404, {}, ''] }
25
+ end
26
+ end
27
+ end
28
+
29
+ let(:response) do
30
+ client.get('/example')
31
+ end
32
+
33
+ it 'raises GarageClient::Error with response summary' do
34
+ expect { raise GarageClient::Error, response.env }.to raise_error(GarageClient::Error, /GET .+ 404/)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ describe GarageClient::Request::JsonEncoded do
4
+ let(:client) do
5
+ GarageClient::Client.new(headers: headers)
6
+ end
7
+
8
+ let(:headers) do
9
+ {}
10
+ end
11
+
12
+ let(:params) do
13
+ { key: "value" }
14
+ end
15
+
16
+ context "with Content-Type: application/json" do
17
+ let(:headers) do
18
+ { "Content-Type" => "application/json" }
19
+ end
20
+
21
+ it "encodes request body to JSON" do
22
+ stub_post("/examples").with(body: params.to_json)
23
+ expect { client.post("/examples", params) }.not_to raise_error
24
+ end
25
+ end
26
+
27
+ context "without Content-Type" do
28
+ it "encodes request body to JSON" do
29
+ stub_post("/examples").with(body: params.to_json)
30
+ expect { client.post("/examples", params) }.not_to raise_error
31
+ end
32
+ end
33
+
34
+ context "without body" do
35
+ let(:params) do
36
+ nil
37
+ end
38
+
39
+ it "does nothing" do
40
+ stub_post("/examples").with(body: nil)
41
+ expect { client.post("/examples", params) }.not_to raise_error
42
+ end
43
+ end
44
+
45
+
46
+ context "with Content-Type: multipart/form-data" do
47
+ let(:headers) do
48
+ { "Content-Type" => "multipart/form-data" }
49
+ end
50
+
51
+ it "does nothing" do
52
+ stub_post("/examples").with(
53
+ body: [
54
+ "-------------RubyMultipartPost",
55
+ "Content-Disposition: form-data; name=\"key\"",
56
+ "",
57
+ "value",
58
+ "-------------RubyMultipartPost--",
59
+ "",
60
+ "",
61
+ ].join("\r\n")
62
+ )
63
+ expect { client.post("/examples", params) }.not_to raise_error
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe GarageClient::Resource do
4
+ let(:client) { GarageClient::Client.new }
5
+ let(:response_body) { JSON.parse(fixture('example.yaml')['body']) }
6
+ let(:resource) { GarageClient::Resource.new(client, response_body) }
7
+
8
+ describe '#properties' do
9
+ it 'returns available properties' do
10
+ resource.properties.should include(
11
+ :id,
12
+ :created,
13
+ :updated,
14
+ :name,
15
+ :url,
16
+ :description,
17
+ :serving,
18
+ :published,
19
+ :edited,
20
+ :tier,
21
+ :ingredients,
22
+ :steps
23
+ )
24
+ end
25
+ end
26
+
27
+ describe '#links' do
28
+ it 'returns available links' do
29
+ resource.links.should include(:self, :canonical, :nested_examples)
30
+ end
31
+ end
32
+
33
+ describe '#update' do
34
+ before do
35
+ stub_put('/examples/1').to_return(fixture('example.yaml'))
36
+ end
37
+
38
+ it 'returns response with updated resource' do
39
+ response = resource.update(:name => 'new name')
40
+ response.should be_kind_of(GarageClient::Response)
41
+ response.body.should be_kind_of(GarageClient::Resource)
42
+ end
43
+ end
44
+
45
+ describe '#destroy' do
46
+ before do
47
+ stub_delete('/examples/1').to_return(:status => 204, :body => '')
48
+ end
49
+
50
+ it 'returns response' do
51
+ response = resource.destroy
52
+ response.should be_kind_of(GarageClient::Response)
53
+ response.body.should be_nil
54
+ end
55
+ end
56
+
57
+ describe 'create nested resource' do
58
+ before do
59
+ stub_post('/examples/1/nested_examples').to_return(fixture('example.yaml'))
60
+ end
61
+
62
+ it 'returns response' do
63
+ response = resource.create_nested_examples(:name => 'name')
64
+ response.should be_kind_of(GarageClient::Response)
65
+ response.body.should be_kind_of(GarageClient::Resource)
66
+ end
67
+ end
68
+
69
+ describe 'property' do
70
+ context 'with non-existent property' do
71
+ it 'raise no method error' do
72
+ expect { resource.non_existent_field }.to raise_error(NoMethodError)
73
+ end
74
+ end
75
+
76
+ context 'with primitive value' do
77
+ it 'returns value' do
78
+ resource.name.should == 'recipe title'
79
+ end
80
+ end
81
+
82
+ context 'with resource value' do
83
+ it 'returns resource' do
84
+ resource.user.should be_kind_of(GarageClient::Resource)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe 'link' do
90
+ context 'with existent link' do
91
+ before do
92
+ stub_get('/examples/1/nested_examples').to_return(fixture('examples.yaml'))
93
+ end
94
+
95
+ it 'returns response' do
96
+ response = resource.nested_examples
97
+ response.should be_kind_of(GarageClient::Response)
98
+ response.body.should be_kind_of(Array)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,450 @@
1
+ require 'spec_helper'
2
+
3
+ describe GarageClient::Response do
4
+ let(:response) do
5
+ described_class.new(client, raw_response)
6
+ end
7
+
8
+ let(:client) do
9
+ GarageClient::Client.new
10
+ end
11
+
12
+ let(:raw_response) do
13
+ double(headers: headers, body: env.body, env: env)
14
+ end
15
+
16
+ let(:headers) do
17
+ { 'Link' => link }
18
+ end
19
+
20
+ let(:env) do
21
+ double(:env, body: body).tap do |e|
22
+ allow(e).to receive(:[]).with(:body).and_return(body)
23
+ end
24
+ end
25
+
26
+ let(:body) do
27
+ {}
28
+ end
29
+
30
+ let(:link) do
31
+ %w[
32
+ </v1/examples?page=1>; rel="first",
33
+ </v1/examples?page=2>; rel="prev",
34
+ </v1/examples?page=4>; rel="next",
35
+ </v1/examples?page=5>; rel="last"
36
+ ].join(" ")
37
+ end
38
+
39
+ describe "#respond_to?" do
40
+ context "with same property" do
41
+ before do
42
+ body["name"] = "example"
43
+ end
44
+
45
+ it "returns true" do
46
+ response.respond_to?(:name).should == true
47
+ end
48
+ end
49
+
50
+ context "with same method" do
51
+ it "returns true" do
52
+ response.respond_to?(:body).should == true
53
+ end
54
+ end
55
+
56
+ context "with neithor same property nor same method" do
57
+ it "returns false" do
58
+ response.respond_to?(:name).should == false
59
+ end
60
+ end
61
+
62
+ context "with private method name and no include_private option" do
63
+ it "returns false" do
64
+ response.respond_to?(:parsed_link_header).should == false
65
+ end
66
+ end
67
+
68
+ context "with private method name and include_private option" do
69
+ it "returns true" do
70
+ response.respond_to?(:parsed_link_header, true).should == true
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#has_next_page?" do
76
+ context "without Link header" do
77
+ before do
78
+ headers.delete("Link")
79
+ end
80
+
81
+ it "returns false" do
82
+ response.has_next_page?.should == false
83
+ end
84
+ end
85
+
86
+ context "without next link" do
87
+ let(:link) do
88
+ ""
89
+ end
90
+
91
+ it "returns false" do
92
+ response.has_next_page?.should == false
93
+ end
94
+ end
95
+
96
+ context "with next link" do
97
+ it "returns true" do
98
+ response.has_next_page?.should == true
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#has_prev_page?" do
104
+ context "without Link header" do
105
+ before do
106
+ headers.delete("Link")
107
+ end
108
+
109
+ it "returns false" do
110
+ response.has_prev_page?.should == false
111
+ end
112
+ end
113
+
114
+ context "without prev link" do
115
+ let(:link) do
116
+ ""
117
+ end
118
+
119
+ it "returns false" do
120
+ response.has_prev_page?.should == false
121
+ end
122
+ end
123
+
124
+ context "with prev link" do
125
+ it "returns true" do
126
+ response.has_prev_page?.should == true
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#has_first_page?" do
132
+ context "without Link header" do
133
+ before do
134
+ headers.delete("Link")
135
+ end
136
+
137
+ it "returns false" do
138
+ response.has_first_page?.should == false
139
+ end
140
+ end
141
+
142
+ context "without first link" do
143
+ let(:link) do
144
+ ""
145
+ end
146
+
147
+ it "returns false" do
148
+ response.has_first_page?.should == false
149
+ end
150
+ end
151
+
152
+ context "with first link" do
153
+ it "returns true" do
154
+ response.has_first_page?.should == true
155
+ end
156
+ end
157
+ end
158
+
159
+ describe "#has_last_page?" do
160
+ context "without Link header" do
161
+ before do
162
+ headers.delete("Link")
163
+ end
164
+
165
+ it "returns false" do
166
+ response.has_last_page?.should == false
167
+ end
168
+ end
169
+
170
+ context "without last link" do
171
+ let(:link) do
172
+ ""
173
+ end
174
+
175
+ it "returns false" do
176
+ response.has_last_page?.should == false
177
+ end
178
+ end
179
+
180
+ context "with last link" do
181
+ it "returns true" do
182
+ response.has_last_page?.should == true
183
+ end
184
+ end
185
+ end
186
+
187
+ describe "#next_page_path" do
188
+ context "without Link header" do
189
+ before do
190
+ headers.delete("Link")
191
+ end
192
+
193
+ it "returns nil" do
194
+ response.next_page_path.should == nil
195
+ end
196
+ end
197
+
198
+ context "without next link" do
199
+ let(:link) do
200
+ ""
201
+ end
202
+
203
+ it "returns nil" do
204
+ response.next_page_path.should == nil
205
+ end
206
+ end
207
+
208
+ context "with next link" do
209
+ it "returns next page path" do
210
+ response.next_page_path.should == "/v1/examples?page=4"
211
+ end
212
+ end
213
+ end
214
+
215
+ describe "#prev_page_path" do
216
+ context "without Link header" do
217
+ before do
218
+ headers.delete("Link")
219
+ end
220
+
221
+ it "returns nil" do
222
+ response.prev_page_path.should == nil
223
+ end
224
+ end
225
+
226
+ context "without prev link" do
227
+ let(:link) do
228
+ ""
229
+ end
230
+
231
+ it "returns nil" do
232
+ response.prev_page_path.should == nil
233
+ end
234
+ end
235
+
236
+ context "with prev link" do
237
+ it "returns prev page path" do
238
+ response.prev_page_path.should == "/v1/examples?page=2"
239
+ end
240
+ end
241
+ end
242
+
243
+ describe "#first_page_path" do
244
+ context "without Link header" do
245
+ before do
246
+ headers.delete("Link")
247
+ end
248
+
249
+ it "returns nil" do
250
+ response.first_page_path.should == nil
251
+ end
252
+ end
253
+
254
+ context "without first link" do
255
+ let(:link) do
256
+ ""
257
+ end
258
+
259
+ it "returns nil" do
260
+ response.first_page_path.should == nil
261
+ end
262
+ end
263
+
264
+ context "with first link" do
265
+ it "returns first page path" do
266
+ response.first_page_path.should == "/v1/examples?page=1"
267
+ end
268
+ end
269
+ end
270
+
271
+ describe "#last_page_path" do
272
+ context "without Link header" do
273
+ before do
274
+ headers.delete("Link")
275
+ end
276
+
277
+ it "returns nil" do
278
+ response.last_page_path.should == nil
279
+ end
280
+ end
281
+
282
+ context "without last link" do
283
+ let(:link) do
284
+ ""
285
+ end
286
+
287
+ it "returns nil" do
288
+ response.last_page_path.should == nil
289
+ end
290
+ end
291
+
292
+ context "with last link" do
293
+ it "returns last page path" do
294
+ response.last_page_path.should == "/v1/examples?page=5"
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ describe Faraday::Response do
301
+ let(:mime_dict) { 'application/vnd.cookpad.dictionary+json' }
302
+ let(:client) { GarageClient::Client.new }
303
+ let(:response) { client.get('/examples') }
304
+
305
+ describe '#link' do
306
+ context 'with resources collection' do
307
+ context 'without paginated resources' do
308
+ before do
309
+ stub_get('/examples').to_return(fixture('examples_without_pagination.yaml'))
310
+ end
311
+
312
+ it 'returns link header' do
313
+ response.link.should be_nil
314
+ end
315
+ end
316
+
317
+ context 'with paginated resources' do
318
+ before do
319
+ stub_get('/examples').to_return(fixture('examples.yaml'))
320
+ end
321
+
322
+ it 'returns link header' do
323
+ response.link.should == %q{</v1/examples?page=2&per_page=1>; rel="next"}
324
+ end
325
+ end
326
+ end
327
+ end
328
+
329
+ describe '#total_count' do
330
+ context 'with resources collection' do
331
+ context 'without paginated resources' do
332
+ before do
333
+ stub_get('/examples').to_return(fixture('examples_without_pagination.yaml'))
334
+ end
335
+
336
+ it 'returns nil' do
337
+ response.total_count.should be_nil
338
+ end
339
+ end
340
+
341
+ context 'with paginated resources' do
342
+ before do
343
+ stub_get('/examples').to_return(fixture('examples.yaml'))
344
+ end
345
+
346
+ it 'returns total count' do
347
+ response.total_count.should == 1
348
+ end
349
+ end
350
+ end
351
+ end
352
+
353
+ describe '#body' do
354
+ let(:single_response) { client.get('/examples/1') }
355
+ let(:array_response) { client.get('/examples') }
356
+ let(:dictionary_response) { client.get('/examples', nil, :headers => { 'Accept' => mime_dict }) }
357
+
358
+ context 'with single resource' do
359
+ let(:response) { single_response }
360
+
361
+ before do
362
+ stub_get('/examples/1').to_return(fixture('example.yaml'))
363
+ end
364
+
365
+ it 'returns resource' do
366
+ response.body.should be_kind_of(GarageClient::Resource)
367
+ end
368
+ end
369
+
370
+ context 'with resources collection' do
371
+ context 'with array response' do
372
+ let(:response) { array_response }
373
+
374
+ before do
375
+ stub_get('/examples').to_return(fixture('examples.yaml'))
376
+ end
377
+
378
+ it 'returns resources array' do
379
+ response.body.should be_kind_of(Array)
380
+ response.body.first.should be_kind_of(GarageClient::Resource)
381
+ end
382
+ end
383
+
384
+ context 'with dictionary response' do
385
+ let(:response) { dictionary_response }
386
+
387
+ before do
388
+ stub_get('/examples').to_return(fixture('examples_dictionary.yaml'))
389
+ end
390
+
391
+ it 'returns resources array' do
392
+ response.body.should be_kind_of(Hash)
393
+ response.body['1'].should be_kind_of(GarageClient::Resource)
394
+ end
395
+ end
396
+ end
397
+ end
398
+
399
+ describe 'delegation' do
400
+ before do
401
+ stub_get('/examples').to_return(fixture('examples.yaml'))
402
+ end
403
+
404
+ it 'delegates undefined method call to resource' do
405
+ response.size.should == 1
406
+ response.first.should be_kind_of(GarageClient::Resource)
407
+ end
408
+ end
409
+
410
+ describe 'http errors' do
411
+ {
412
+ 401 => GarageClient::Unauthorized,
413
+ 403 => GarageClient::Forbidden,
414
+ 404 => GarageClient::NotFound,
415
+ 406 => GarageClient::NotAcceptable,
416
+ 409 => GarageClient::Conflict,
417
+ 415 => GarageClient::UnsupportedMediaType,
418
+ 422 => GarageClient::UnprocessableEntity,
419
+ 500 => GarageClient::InternalServerError,
420
+ 503 => GarageClient::ServiceUnavailable,
421
+ }.each do |status, exception|
422
+ context "when HTTP status is #{status}" do
423
+ before do
424
+ stub_get('/examples/xyz').to_return(:status => status)
425
+ end
426
+
427
+ it "should raise #{exception.name} error" do
428
+ expect {
429
+ client.get('/examples/xyz')
430
+ }.to raise_error(exception) { |e|
431
+ e.should be_a_kind_of(GarageClient::Error)
432
+ e.response.should be_respond_to(:[])
433
+ }
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ describe 'response type error' do
440
+ before do
441
+ stub_get('/examples').to_return(body: 'error')
442
+ end
443
+
444
+ it 'raises error' do
445
+ expect { client.get('/examples') }.to raise_error(GarageClient::InvalidResponseType) {|e|
446
+ e.should be_a_kind_of(GarageClient::Error)
447
+ }
448
+ end
449
+ end
450
+ end