springboard-retail 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +1 -0
  6. data/Gemfile +14 -0
  7. data/Gemfile.lock +66 -0
  8. data/LICENSE +21 -0
  9. data/README.md +253 -0
  10. data/Rakefile +16 -0
  11. data/lib/springboard-retail.rb +1 -0
  12. data/lib/springboard/client.rb +263 -0
  13. data/lib/springboard/client/body.rb +12 -0
  14. data/lib/springboard/client/collection.rb +108 -0
  15. data/lib/springboard/client/errors.rb +17 -0
  16. data/lib/springboard/client/resource.rb +205 -0
  17. data/lib/springboard/client/response.rb +85 -0
  18. data/lib/springboard/client/uri_ext.rb +51 -0
  19. data/spec/shared_client_context.rb +5 -0
  20. data/spec/spec_helper.rb +12 -0
  21. data/spec/springboard/client/body_spec.rb +32 -0
  22. data/spec/springboard/client/resource_spec.rb +250 -0
  23. data/spec/springboard/client/response_spec.rb +100 -0
  24. data/spec/springboard/client/uri_ext_spec.rb +51 -0
  25. data/spec/springboard/client_spec.rb +214 -0
  26. data/springboard-retail.gemspec +20 -0
  27. data/vendor/cache/addressable-2.2.8.gem +0 -0
  28. data/vendor/cache/coderay-1.0.7.gem +0 -0
  29. data/vendor/cache/coveralls-0.6.9.gem +0 -0
  30. data/vendor/cache/crack-0.3.1.gem +0 -0
  31. data/vendor/cache/diff-lcs-1.1.3.gem +0 -0
  32. data/vendor/cache/hashie-2.0.5.gem +0 -0
  33. data/vendor/cache/json-1.8.1.gem +0 -0
  34. data/vendor/cache/method_source-0.8.gem +0 -0
  35. data/vendor/cache/mime-types-1.25.gem +0 -0
  36. data/vendor/cache/multi_json-1.8.0.gem +0 -0
  37. data/vendor/cache/patron-0.4.18.gem +0 -0
  38. data/vendor/cache/pry-0.9.10.gem +0 -0
  39. data/vendor/cache/rake-0.9.2.2.gem +0 -0
  40. data/vendor/cache/rest-client-1.6.7.gem +0 -0
  41. data/vendor/cache/rspec-2.11.0.gem +0 -0
  42. data/vendor/cache/rspec-core-2.11.1.gem +0 -0
  43. data/vendor/cache/rspec-expectations-2.11.2.gem +0 -0
  44. data/vendor/cache/rspec-mocks-2.11.1.gem +0 -0
  45. data/vendor/cache/simplecov-0.7.1.gem +0 -0
  46. data/vendor/cache/simplecov-html-0.7.1.gem +0 -0
  47. data/vendor/cache/slop-3.3.2.gem +0 -0
  48. data/vendor/cache/term-ansicolor-1.2.2.gem +0 -0
  49. data/vendor/cache/thor-0.18.1.gem +0 -0
  50. data/vendor/cache/tins-0.9.0.gem +0 -0
  51. data/vendor/cache/webmock-1.8.8.gem +0 -0
  52. metadata +148 -0
@@ -0,0 +1,5 @@
1
+ shared_context "client" do
2
+ let(:base_url) { "http://bozo.com/api" }
3
+ let(:client) { Springboard::Client.new(base_url) }
4
+ let(:session) { client.session }
5
+ end
@@ -0,0 +1,12 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'springboard-retail'
5
+ require 'webmock/rspec'
6
+ require 'shared_client_context'
7
+
8
+ class String
9
+ def to_uri
10
+ Addressable::URI.parse(self)
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Springboard::Client::Body do
4
+ let(:hash) { {"key1" => "val1", "key2" => {"subkey1" => "subval1"}} }
5
+ let(:body) { Springboard::Client::Body.new(hash)}
6
+
7
+ describe "[]" do
8
+ it "should support string keys" do
9
+ body["key1"].should == "val1"
10
+ end
11
+
12
+ it "should support symbol keys" do
13
+ body[:key1].should == "val1"
14
+ end
15
+ end
16
+
17
+ describe "nested hashes" do
18
+ it "should support nested indifferent access" do
19
+ body[:key2][:subkey1].should == "subval1"
20
+ body['key2']['subkey1'].should == "subval1"
21
+ body[:key2]['subkey1'].should == "subval1"
22
+ body['key2'][:subkey1].should == "subval1"
23
+ end
24
+ end
25
+
26
+ describe "to_hash" do
27
+ it "should return the original hash" do
28
+ body.to_hash.should === hash
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+
3
+ describe Springboard::Client::Resource do
4
+ include_context "client"
5
+
6
+ let(:resource_path) { '/some/path' }
7
+ let(:resource) { Springboard::Client::Resource.new(client, resource_path) }
8
+
9
+ def parse_uri(uri)
10
+ Addressable::URI.parse(uri)
11
+ end
12
+
13
+ describe "[]" do
14
+ it "should return a new resource" do
15
+ resource["subpath"].should be_a Springboard::Client::Resource
16
+ resource["subpath"].object_id.should_not == resource.object_id
17
+ end
18
+
19
+ it "should return a resource with the given subpath appended to its URI" do
20
+ resource["subpath"].uri.to_s.should == "/some/path/subpath"
21
+ end
22
+
23
+ it "should return a resource with the same client instance" do
24
+ resource["subpath"].client.should === resource.client
25
+ end
26
+
27
+ it "should accept a symbol as a path" do
28
+ resource[:subpath].uri.to_s.should == "/some/path/subpath"
29
+ end
30
+
31
+ it "should accept a symbol as a path" do
32
+ resource[:subpath].uri.to_s.should == "/some/path/subpath"
33
+ end
34
+
35
+ it "should not URI encode the given subpath" do
36
+ resource["subpath with spaces"].uri.to_s.should == "/some/path/subpath with spaces"
37
+ end
38
+ end
39
+
40
+ %w{query params}.each do |method|
41
+ describe method do
42
+ describe "when called with a hash" do
43
+ it "should set the query string parameters" do
44
+ resource.__send__(method, :a => 1, :b => 2).uri.to_s.should == "/some/path?a=1&b=2"
45
+ end
46
+
47
+ it "should URL encode the given keys and values" do
48
+ resource.__send__(method, "i have spaces" => "so do i: duh").uri.to_s.
49
+ should == "/some/path?i%20have%20spaces=so%20do%20i%3A%20duh"
50
+ end
51
+
52
+ it "should add bracket notation for array parameters" do
53
+ resource.__send__(method, :somearray => [1, 2, 3]).uri.to_s.should == "/some/path?somearray[]=1&somearray[]=2&somearray[]=3"
54
+ end
55
+ end
56
+
57
+ describe "when called without arguments" do
58
+ it "should return the current query string parameters as a hash" do
59
+ resource.__send__(method).should == {}
60
+ new_resource = resource.__send__(method, :a => 1, :b => 2)
61
+ new_resource.__send__(method).should == {"a"=>"1", "b"=>"2"}
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "filter" do
68
+ describe "when given a hash" do
69
+ it "should add a _filter query string param" do
70
+ resource.filter(:a => 1, :b => 2).uri.should ==
71
+ '/some/path?_filter={"a":1,"b":2}'.to_uri
72
+ end
73
+ end
74
+
75
+ describe "when called multiple times" do
76
+ it "should append args to _filter param as JSON array" do
77
+ resource.filter(:a => 1).filter(:b => 2).filter(:c => 3).uri.should ==
78
+ '/some/path?_filter=[{"a":1},{"b":2},{"c":3}]'.to_uri
79
+ end
80
+ end
81
+
82
+ describe "when given a string" do
83
+ it "should add a _filter query string param" do
84
+ resource.filter('{"a":1,"b":2}').uri.should ==
85
+ '/some/path?_filter={"a":1,"b":2}'.to_uri
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "sort" do
91
+ it "should set the sort parameter based on the given values" do
92
+ resource.sort('f1', 'f2,desc').uri.query.should == 'sort[]=f1&sort[]=f2%2Cdesc'
93
+ end
94
+
95
+ it "should replace any existing sort parameter" do
96
+ resource.sort('f1', 'f2,desc')
97
+ resource.sort('f3,asc', 'f4').uri.query.should == 'sort[]=f3%2Casc&sort[]=f4'
98
+ end
99
+ end
100
+
101
+ %w{count each each_page}.each do |method|
102
+ describe method do
103
+ it "should call the client's #{method} method with the resource's URI" do
104
+ client.should_receive(method).with(resource.uri)
105
+ resource.__send__(method)
106
+ end
107
+ end
108
+ end
109
+
110
+ %w{get head delete}.each do |method|
111
+ describe method do
112
+ it "should call the client's #{method} method with the resource's URI and a header hash" do
113
+ client.should_receive(method).with(resource.uri, false)
114
+ resource.__send__(method)
115
+ end
116
+ end
117
+ end
118
+
119
+ %w{put post}.each do |method|
120
+ describe method do
121
+ it "should call the client's #{method} method with the resource's URI, the given body, and a headers hash" do
122
+ client.should_receive(method).with(resource.uri, "body", false)
123
+ resource.__send__(method, "body")
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "first" do
129
+ let(:response_data) {
130
+ {
131
+ :status => 200,
132
+ :body => {:results => [{:id => "Me first!"}, {:id => "Me second!"}]}.to_json
133
+ }
134
+ }
135
+
136
+ it "should set the per_page query string param to 1" do
137
+ request_stub = stub_request(:get, "#{base_url}/some/path?page=1&per_page=1").to_return(response_data)
138
+ resource.first
139
+ request_stub.should have_been_requested
140
+ end
141
+
142
+ it "should return the first element of the :results array" do
143
+ request_stub = stub_request(:get, "#{base_url}/some/path?page=1&per_page=1").to_return(response_data)
144
+ resource.first.should == {"id" => "Me first!"}
145
+ end
146
+ end
147
+
148
+ describe "embed" do
149
+ it "should support a single embed" do
150
+ resource.embed(:thing1).uri.to_s.should ==
151
+ '/some/path?_include[]=thing1'
152
+ end
153
+
154
+ it "should support multiple embeds" do
155
+ resource.embed(:thing1, :thing2, :thing3).uri.to_s.should ==
156
+ '/some/path?_include[]=thing1&_include[]=thing2&_include[]=thing3'
157
+ end
158
+
159
+ it "should merge multiple embed calls" do
160
+ resource.embed(:thing1, :thing2).embed(:thing3, :thing4).uri.to_s.should ==
161
+ '/some/path?_include[]=thing1&_include[]=thing2&_include[]=thing3&_include[]=thing4'
162
+ end
163
+
164
+ it "should merge multiple embed calls" do
165
+ resource.embed(:thing1, :thing2).embed(:thing3, :thing4).uri.to_s.should ==
166
+ '/some/path?_include[]=thing1&_include[]=thing2&_include[]=thing3&_include[]=thing4'
167
+ end
168
+
169
+ it "should merge a call to embed with a manually added _include query param" do
170
+ resource.query('_include[]' => :thing1).embed(:thing2, :thing3).uri.to_s.should ==
171
+ '/some/path?_include[]=thing1&_include[]=thing2&_include[]=thing3'
172
+ end
173
+ end
174
+
175
+ describe "while_results" do
176
+ it "should yield each result to the block as long as the response includes results" do
177
+ results = ["r1", "r2", "r3"]
178
+
179
+ request_stub = stub_request(:get, "#{base_url}/some/path").to_return do |req|
180
+ {:body => {:results => results}.to_json}
181
+ end
182
+
183
+ yielded_results = []
184
+
185
+ # timeout in case of endless loop
186
+ Timeout::timeout(1) do
187
+ resource.while_results do |result|
188
+ yielded_results.push results.shift
189
+ end
190
+ end
191
+
192
+ yielded_results.should == ["r1", "r2", "r3"]
193
+ end
194
+
195
+ it "should raise an exception if it receives an error response" do
196
+ request_stub = stub_request(:get, "#{base_url}/some/path").to_return do |req|
197
+ {:status => 400}
198
+ end
199
+
200
+ # timeout in case of endless loop
201
+ Timeout::timeout(1) do
202
+ expect do
203
+ resource.while_results do |result|
204
+ # nothing
205
+ end
206
+ end.to raise_error(Springboard::Client::RequestFailed)
207
+ end
208
+ end
209
+
210
+ describe "exists?" do
211
+ let(:response) { mock(Springboard::Client::Response) }
212
+
213
+ it "should return true if the response indicates success" do
214
+ response.stub!(:success?).and_return(true)
215
+ client.should_receive(:head).with(resource.uri, false).and_return(response)
216
+ resource.exists?.should === true
217
+ end
218
+
219
+ it "should return false if the response status is 404" do
220
+ response.stub!(:status).and_return(404)
221
+ response.stub!(:success?).and_return(false)
222
+ client.should_receive(:head).with(resource.uri, false).and_return(response)
223
+ resource.exists?.should === false
224
+ end
225
+
226
+ it "should raise a RequestFailed exception if the request fails but the status is not 404" do
227
+ response.stub!(:status).and_return(400)
228
+ response.stub!(:success?).and_return(false)
229
+ client.should_receive(:head).with(resource.uri, false).and_return(response)
230
+ expect { resource.exists? }.to raise_error { |e|
231
+ e.should be_a Springboard::Client::RequestFailed
232
+ e.response.should === response
233
+ e.message.should == "Request during call to 'exists?' resulted in non-404 error."
234
+ }
235
+ end
236
+ end
237
+
238
+ describe "empty?" do
239
+ it "should return true if the resource has a count of zero" do
240
+ resource.stub!(:count).and_return 0
241
+ resource.empty?.should === true
242
+ end
243
+
244
+ it "should return false if the resource has a count greater than zero" do
245
+ resource.stub!(:count).and_return 10
246
+ resource.empty?.should === false
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Springboard::Client::Response do
4
+ include_context "client"
5
+
6
+ let(:raw_body) { '{"key":"value"}' }
7
+ let(:raw_headers) { 'X-Custom-Header: Hi' }
8
+ let(:status_code) { 200 }
9
+ let(:path) { '/path' }
10
+ let(:patron_response) { Patron::Response.new(path, status_code, 0, raw_headers, raw_body) }
11
+ let(:response) { Springboard::Client::Response.new(patron_response, client) }
12
+
13
+ describe "body" do
14
+ describe "if raw body is valid JSON" do
15
+ it "should return a Springboard::Client::Body" do
16
+ response.body.should be_a Springboard::Client::Body
17
+ end
18
+
19
+ it "should wrap the parsed response body" do
20
+ response.body.to_hash.should == {"key" => "value"}
21
+ end
22
+ end
23
+
24
+ describe "if raw body is not valid JSON" do
25
+ let(:raw_body) { 'I am not JSON!' }
26
+ it "should raise an informative error" do
27
+ expect { response.body }.to raise_error \
28
+ Springboard::Client::BodyError,
29
+ "Can't parse response body. (Hint: Try the raw_body method.)"
30
+ end
31
+ end
32
+
33
+ describe "if raw body is empty" do
34
+ let(:raw_body) { '' }
35
+ it "should raise an informative error" do
36
+ expect { response.body }.to raise_error \
37
+ Springboard::Client::BodyError,
38
+ "Response body is empty. (Hint: If you just created a new resource, try: response.resource.get)"
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "raw_body" do
44
+ it "should return the raw body JSON" do
45
+ response.raw_body.should == raw_body
46
+ end
47
+ end
48
+
49
+ describe "headers" do
50
+ it "should return the response headers as a hash" do
51
+ response.headers.should == {'X-Custom-Header' => 'Hi'}
52
+ end
53
+ end
54
+
55
+ describe "resource" do
56
+ describe "when Location header is returned" do
57
+ let(:raw_headers) { 'Location: /new/path' }
58
+
59
+ it "should be a Springboard::Client::Resource" do
60
+ response.resource.should be_a Springboard::Client::Resource
61
+ end
62
+
63
+ it "should have the Location header value as its URL" do
64
+ response.resource.uri.to_s.should == '/new/path'
65
+ end
66
+ end
67
+
68
+ describe "when Location header is not returned" do
69
+ let(:raw_headers) { '' }
70
+
71
+ it "should be nil" do
72
+ response.resource.should be_nil
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "[]" do
78
+ it "should forward [] to body" do
79
+ response.body.should_receive(:[]).with("key").and_return("value")
80
+ response["key"].should == "value"
81
+ end
82
+ end
83
+
84
+ describe "success?" do
85
+ %w{100 101 102 200 201 202 203 204 205 206 207 208 226 300 301 302 303 304 305 306 307 308}.each do |code|
86
+ it "should return true if the response status code is #{code}" do
87
+ response.stub!(:status).and_return(code.to_i)
88
+ response.success?.should === true
89
+ end
90
+ end
91
+
92
+ %w{400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 425 426 428 429 431
93
+ 444 449 450 499 500 501 502 503 504 505 506 507 508 509 510 511 598 599}.each do |code|
94
+ it "should return false if the response status code is #{code}" do
95
+ response.stub!(:status).and_return(code.to_i)
96
+ response.success?.should === false
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Addressable::URI do
4
+ let(:uri) { Addressable::URI.parse('/relative/path') }
5
+
6
+ describe "subpath" do
7
+ it "should return a new URI with the path relative to the receiver" do
8
+ uri.subpath('other').should == Addressable::URI.parse('/relative/path/other')
9
+ uri.subpath('/other').should == Addressable::URI.parse('/relative/path/other')
10
+ uri.subpath(Addressable::URI.parse('/other')) == Addressable::URI.parse('/relative/path/other')
11
+ end
12
+ end
13
+
14
+ describe "merge_query_values!" do
15
+ it "should call springboard_query_values=" do
16
+ uri.query_values = {'a' => '1'}
17
+ uri.should_receive(:springboard_query_values=).with({'a' => '1', 'b' => '2'})
18
+ uri.merge_query_values! 'b' => '2'
19
+ end
20
+
21
+ it "should merge the given values with the existing query_values" do
22
+ uri.query_values = {'a' => '1', 'b' => '2'}
23
+ uri.merge_query_values! 'b' => '20', 'c' => '30'
24
+ uri.query_values.should == {'a' => '1', 'b' => '20', 'c' => '30'}
25
+ end
26
+
27
+ it "should set the given values if there are no existing query_values" do
28
+ uri.query_values.should be_nil
29
+ uri.merge_query_values! 'b' => '20', 'c' => '30'
30
+ uri.query_values.should == {'b' => '20', 'c' => '30'}
31
+ end
32
+ end
33
+
34
+ describe "springboard_query_values=" do
35
+ it "should preserve empty bracket notation for array params" do
36
+ uri.query = 'sort[]=f1&sort[]=f2'
37
+ uri.__send__(:springboard_query_values=, uri.query_values)
38
+ uri.to_s.should == '/relative/path?sort[]=f1&sort[]=f2'
39
+ end
40
+
41
+ it "should stringify boolean param values" do
42
+ uri.__send__(:springboard_query_values=, {:p1 => true, :p2 => false})
43
+ uri.to_s.should == '/relative/path?p1=true&p2=false'
44
+ end
45
+
46
+ it "should support hash param values" do
47
+ uri.__send__(:springboard_query_values=, {:a => {:b => {:c => 123}}})
48
+ uri.to_s.should == '/relative/path?a[b][c]=123'
49
+ end
50
+ end
51
+ end