garage_client 2.1.1

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 (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