well_rested 0.6.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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +27 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +140 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/well_rested +27 -0
- data/examples/hn_search.rb +29 -0
- data/lib/generic_utils.rb +17 -0
- data/lib/key_transformer.rb +43 -0
- data/lib/well_rested.rb +35 -0
- data/lib/well_rested/api.rb +343 -0
- data/lib/well_rested/base.rb +290 -0
- data/lib/well_rested/camel_case_formatter.rb +15 -0
- data/lib/well_rested/json_formatter.rb +12 -0
- data/lib/well_rested/utils.rb +26 -0
- data/spec/api_spec.rb +619 -0
- data/spec/base_spec.rb +352 -0
- data/spec/generic_utils_spec.rb +13 -0
- data/spec/key_transformer_spec.rb +42 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/spec_spec.rb +18 -0
- data/spec/support/api.rb +26 -0
- data/spec/utils_spec.rb +37 -0
- data/spec/well_rested_spec.rb +4 -0
- data/well_rested.gemspec +107 -0
- metadata +295 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module WellRested
|
|
2
|
+
class CamelCaseFormatter
|
|
3
|
+
def initialize(lower = true)
|
|
4
|
+
raise "Upper case camelizing not supported yet" unless lower # TODO: Support upper-camel-casing
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def encode(hash)
|
|
8
|
+
KeyTransformer.camelize_keys(hash)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def decode(hash)
|
|
12
|
+
KeyTransformer.underscore_keys(hash)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'generic_utils'
|
|
2
|
+
|
|
3
|
+
module WellRested
|
|
4
|
+
module Utils
|
|
5
|
+
extend GenericUtils
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# Turn any nested resources back into hashes before sending them
|
|
9
|
+
def objects_to_attributes(obj)
|
|
10
|
+
if obj.respond_to?(:attributes_for_api)
|
|
11
|
+
obj.attributes_for_api
|
|
12
|
+
elsif obj.kind_of?(Hash)
|
|
13
|
+
new_attributes = {}.with_indifferent_access
|
|
14
|
+
obj.each do |k, v|
|
|
15
|
+
new_attributes[k] = objects_to_attributes(v)
|
|
16
|
+
end
|
|
17
|
+
new_attributes
|
|
18
|
+
elsif obj.kind_of?(Array)
|
|
19
|
+
obj.map { |e| self.objects_to_attributes(e) }
|
|
20
|
+
else
|
|
21
|
+
obj
|
|
22
|
+
#raise "Attributes was not a Hash or Enumerable"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/spec/api_spec.rb
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
4
|
+
|
|
5
|
+
describe API do
|
|
6
|
+
before(:each) do
|
|
7
|
+
@account_class = Class.new(Base) do
|
|
8
|
+
self.path = '/accounts'
|
|
9
|
+
end
|
|
10
|
+
@user_class = Class.new(Base) do
|
|
11
|
+
self.path = '/accounts/:account_id/users'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
@api = mock_api
|
|
17
|
+
FakeWeb.register_uri(:get, %r|/accounts$|, :body => '[{}]')
|
|
18
|
+
FakeWeb.register_uri(:get, %r|/accounts/1$|, :body => '{}')
|
|
19
|
+
FakeWeb.register_uri(:get, %r|/accounts/1/users|, :body => '[{}]', 'x_record_count' => '2')
|
|
20
|
+
FakeWeb.register_uri(:get, %r|/accounts/1/users/1|, :body => '{}')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after do
|
|
24
|
+
FakeWeb.clean_registry
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "class methods" do
|
|
28
|
+
describe ".request_headers" do
|
|
29
|
+
subject { API.request_headers }
|
|
30
|
+
it { should be_a Hash }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '.fill_path' do
|
|
34
|
+
it "should return the path with filled parameters" do
|
|
35
|
+
API.fill_path('/something/:that/has/:parameters', :that => 'foo', :parameters => 'bar').should == '/something/foo/has/bar'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "should raise an exception if there are unmatched parameters" do
|
|
39
|
+
expect { API.fill_path('/something/:that/lacks/:parameters', :that => 'foo') }.should raise_error ArgumentError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should raise an exception if there is a blank param" do
|
|
43
|
+
expect { API.fill_path('/something/:that/lacks/parameters', :that => '') }.should raise_error ArgumentError
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '.request_headers' do
|
|
49
|
+
subject { @api.request_headers }
|
|
50
|
+
it { should be_a Hash }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe ".url_for" do
|
|
54
|
+
it "should substitute path params" do
|
|
55
|
+
@api.url_for(@user_class, :account_id => 1).should match /accounts\/1\/users/
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should append query params when passed a hash" do
|
|
59
|
+
@api.url_for(@account_class, {}, :count => 4).should match /accounts\?count=4$/
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should append query params when passed a string" do
|
|
63
|
+
@api.url_for(@account_class, '/accounts', :count => 4).should match /accounts\?count=4$/
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "should attribute-encode query params" do
|
|
67
|
+
@account_class.attribute_formatter = CamelCaseFormatter.new
|
|
68
|
+
@api.url_for(@account_class, '/accounts', :my_count => 4).should match /accounts\?myCount=4$/
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should use the specified extension" do
|
|
72
|
+
@account_class.extension = '.foobar'
|
|
73
|
+
@api.url_for(@account_class, '/accounts', :my_count => 4).should match /accounts.foobar\?myCount=4$/
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "with auth set" do
|
|
77
|
+
before do
|
|
78
|
+
@api.user = 'admin'
|
|
79
|
+
@api.password = 'password'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should generate a url with auth" do
|
|
83
|
+
@api.url_for(@account_class).should match /:\/\/admin:password@/
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe ".default_path_parameters" do
|
|
89
|
+
context "with empty initializer" do
|
|
90
|
+
subject { @api.default_path_parameters }
|
|
91
|
+
|
|
92
|
+
it { should be_empty }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "with params in initializer" do
|
|
96
|
+
before { @api = API.new(:account_id => 7, :id => 2) }
|
|
97
|
+
|
|
98
|
+
it "should have the passed value" do
|
|
99
|
+
@api.default_path_parameters.should == { 'account_id' => 7, 'id' => 2 }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "should use the default path params when generating URLs" do
|
|
103
|
+
@api.url_for(@user_class).should =~ /accounts\/7\/users\/2/
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe 'API Calls' do
|
|
109
|
+
describe ".request" do
|
|
110
|
+
it "should issue a PUT request" do
|
|
111
|
+
FakeWeb.register_uri(:put, "#{base_path}/request_put_test", :body => '')
|
|
112
|
+
@api.request(Base, :put, "/request_put_test")
|
|
113
|
+
FakeWeb.should have_requested(:put, "http://#{base_path}/request_put_test")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "should issue a POST request" do
|
|
117
|
+
FakeWeb.register_uri(:post, "#{base_path}/request_post_test", :body => '{}')
|
|
118
|
+
@api.request(Base, :post, "/request_post_test")
|
|
119
|
+
FakeWeb.should have_requested(:post, "http://#{base_path}/request_post_test")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "should issue a GET request" do
|
|
123
|
+
FakeWeb.register_uri(:get, "#{base_path}/request_get_test", :body => '{}')
|
|
124
|
+
@api.request(Base, :get, "/request_get_test")
|
|
125
|
+
FakeWeb.should have_requested(:get, "http://#{base_path}/request_get_test")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "should issue a DELETE request" do
|
|
129
|
+
FakeWeb.register_uri(:delete, "#{base_path}/request_del_test", :body => '')
|
|
130
|
+
@api.request(Base, :delete, "/request_del_test")
|
|
131
|
+
FakeWeb.should have_requested(:delete, "http://#{base_path}/request_del_test")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context "when passed a fully qualified url" do
|
|
135
|
+
before { FakeWeb.register_uri(:get, "#{base_path}/request_get_url", :body => '{}') }
|
|
136
|
+
|
|
137
|
+
it "should use it directly" do
|
|
138
|
+
@api.request(Base, :get, "http://#{base_path}/request_get_url")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "when passed a relative path (beginning with a slash)" do
|
|
143
|
+
before { FakeWeb.register_uri(:get, "#{base_path}/request_get_path", :body => '{}') }
|
|
144
|
+
|
|
145
|
+
it "should fill it out" do
|
|
146
|
+
@api.request(Base, :get, "/request_get_path")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe '.find' do
|
|
152
|
+
context "when passed a url" do
|
|
153
|
+
before {
|
|
154
|
+
@path = "#{base_path}/my/weird/path"
|
|
155
|
+
FakeWeb.register_uri(:get, @path, :body => '{}')
|
|
156
|
+
@account = @api.find(@account_class, '/my/weird/path')
|
|
157
|
+
}
|
|
158
|
+
it "should use the URL" do
|
|
159
|
+
FakeWeb.should have_requested(:get, "http://#{@path}")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "should return a resource of the requested type" do
|
|
163
|
+
@account.should be_an @account_class
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "should raise an exception if the server returns 404" do
|
|
167
|
+
expect { @api.find(@account_class, "/notfound") }.should raise_error
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
context "when passed path params" do
|
|
172
|
+
before { @account = @api.find(@account_class, :id => 1) }
|
|
173
|
+
|
|
174
|
+
it "should return a resource of the requested type" do
|
|
175
|
+
@account.should be_an @account_class
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "should handle query params" do
|
|
179
|
+
FakeWeb.register_uri(:get, %r|/accounts\/1\?test=true|, :body => '{}')
|
|
180
|
+
@api.find(@account_class, {:id => 1}, :test => 'true')
|
|
181
|
+
FakeWeb.should have_requested(:get, %r|/accounts\/1\?test=true|)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "should create the resource using new_from_api" do
|
|
185
|
+
@account_class.should_receive(:new_from_api)
|
|
186
|
+
@api.find(@account_class, :id => 1)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context "it includes a camelized attribute name" do
|
|
191
|
+
before(:each) do
|
|
192
|
+
@api.client.should_receive(:get).and_return '{ "camelizedAttrName" : "value" }'
|
|
193
|
+
@user = @api.find(@account_class, :id => 1)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "should not include camelize attribute names" do
|
|
197
|
+
@user.attributes.should_not include('camelizedAttrName')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "should include decamelized attribute names" do
|
|
201
|
+
@user.attributes['camelized_attr_name'].should == "value"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "when a resource is passed instead of a class and params" do
|
|
206
|
+
before do
|
|
207
|
+
FakeWeb.register_uri(:get, %r|/test/7/foo/4|, :body => '{}')
|
|
208
|
+
end
|
|
209
|
+
it "should use the path_parameters as path parameters" do
|
|
210
|
+
klass = Class.new(Base) do
|
|
211
|
+
self.path = '/test/:account_id/foo'
|
|
212
|
+
end
|
|
213
|
+
res = klass.new(:id => 4, :account_id => 7)
|
|
214
|
+
@api.find(res)
|
|
215
|
+
FakeWeb.should have_requested(:get, %r|/test/7/foo/4|)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
describe '.find_many' do
|
|
222
|
+
it "should allow a URL override" do
|
|
223
|
+
FakeWeb.register_uri(:get, %r|\/alternate\/users\/path|, :body => '[]')
|
|
224
|
+
@api.find_many(@user_class, '/alternate/users/path')
|
|
225
|
+
FakeWeb.should have_requested(:get, %r|\/alternate\/users\/path|)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "should set last_response on @api with headers" do
|
|
229
|
+
@api.last_response.should be_nil # sanity check
|
|
230
|
+
@users = @api.find_many(@user_class, :account_id => 1)
|
|
231
|
+
@api.last_response.headers[:'x_record_count'].should == '2'
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context "with no parameters" do
|
|
235
|
+
before(:each) { @users = @api.find_many(@user_class, :account_id => 1) }
|
|
236
|
+
|
|
237
|
+
it "should return a collection of objects of the requested type" do
|
|
238
|
+
@users.should be_an Array
|
|
239
|
+
@users.size.should be > 0 # sanity check
|
|
240
|
+
@users.each { |a| a.should be_a @user_class }
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context "with parameters" do
|
|
245
|
+
before { @users = @api.find_many(@user_class, {:account_id => 1}, :count => 1) }
|
|
246
|
+
|
|
247
|
+
it "should return the number of objects requested" do
|
|
248
|
+
@users.size.should == 1
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "should create the resources using new_from_api" do
|
|
252
|
+
@user_class.should_receive(:new_from_api).any_number_of_times
|
|
253
|
+
@api.find_many(@user_class, {:account_id => 1}, :count => 1)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe ".create" do
|
|
260
|
+
before do
|
|
261
|
+
FakeWeb.register_uri(:post, %r||, :body => '{ "id": "test" }', :status => 200)
|
|
262
|
+
@klass = Class.new(Base) { self.path = '' }
|
|
263
|
+
@res = @klass.new
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "should allow a URL override" do
|
|
267
|
+
FakeWeb.clean_registry
|
|
268
|
+
FakeWeb.register_uri(:post, %r|/alternative/path|, :body => '{ "id": "test" }')
|
|
269
|
+
@api.create(@klass, {}, '/alternative/path')
|
|
270
|
+
FakeWeb.should have_requested(:post, %r|/alternative/path|)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it "should create the object using new" do
|
|
274
|
+
@klass.should_receive(:new).at_least(:once).and_return(@res)
|
|
275
|
+
@api.create(@klass, {})
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "should call attributes_for_api on the resource" do
|
|
279
|
+
@klass.stub!(:new).and_return(@res)
|
|
280
|
+
@res.should_receive(:attributes_for_api).at_least(:once).and_return({})
|
|
281
|
+
@api.create(@klass)
|
|
282
|
+
end
|
|
283
|
+
it "should not call attributes on the resource" do
|
|
284
|
+
@klass.stub!(:new_from_api).and_return(@res)
|
|
285
|
+
@res.should_not_receive(:attributes)
|
|
286
|
+
@api.create(@klass)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "should camelize keys" do
|
|
290
|
+
attributes = { :underscored_key => 'foo' }
|
|
291
|
+
@api.client.should_receive(:post).with(anything, {:underscoredKey => 'foo'}.to_json, anything).and_return(double(:code => 200, :body => '{}'))
|
|
292
|
+
@api.create(@klass, attributes)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
context "when the resource is valid" do
|
|
296
|
+
before(:each) do
|
|
297
|
+
@attrs = { :account_id => 1, :first_name => 'FirsTest', :last_name => 'LasTest', :email_address => 'a@b' }
|
|
298
|
+
@user_class.new(@attrs).should be_valid # sanity check that user is really valid
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it "should POST to the resource path" do
|
|
302
|
+
user = @api.create(@user_class, @attrs)
|
|
303
|
+
user.should be_a @user_class
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
context "when an ID is set" do
|
|
308
|
+
before { @res = @klass.new(:id => 9) }
|
|
309
|
+
|
|
310
|
+
it "should not issue a POST" do
|
|
311
|
+
@api.client.stub(:put).and_return(double(:code => 200, :body => '{}'))
|
|
312
|
+
@api.client.should_not_receive(:post)
|
|
313
|
+
@api.save(@res)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "should issue a PUT" do
|
|
317
|
+
@api.client.should_receive(:put).and_return(double(:code => 200, :body => '{}'))
|
|
318
|
+
@api.save(@res)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
context "when the resource is invalid" do
|
|
323
|
+
it "should return false" do
|
|
324
|
+
klass = Class.new(Base) do
|
|
325
|
+
def valid?
|
|
326
|
+
false
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
klass.new.should_not be_valid
|
|
330
|
+
|
|
331
|
+
@api.create(klass, {}).should == false
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
context "when saving a new record" do
|
|
336
|
+
it "should mark the record persisted after save completes" do
|
|
337
|
+
@res.should be_new_record # sanity check
|
|
338
|
+
@api.save(@res)
|
|
339
|
+
@res.should_not be_new_record
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
context "when the resource is an array" do
|
|
344
|
+
before do
|
|
345
|
+
@klass = Class.new(Base) do
|
|
346
|
+
self.path = '/array/resource'
|
|
347
|
+
end
|
|
348
|
+
FakeWeb.clean_registry
|
|
349
|
+
FakeWeb.register_uri(:post, %r|/array\/resource$|, :body => '[{"name":"one"}, {"name":"two"}]')
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
it "should return an array when the resource is an array" do
|
|
353
|
+
res = @api.create(@klass)
|
|
354
|
+
res.should be_an Array
|
|
355
|
+
res.first.name.should == "one"
|
|
356
|
+
res.last.name.should == "two"
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
describe '.save' do
|
|
363
|
+
before do
|
|
364
|
+
@klass = Class.new(Base) { self.path = '' }
|
|
365
|
+
@res = @klass.new(:id => 4)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it "should set the correct content-type" do
|
|
369
|
+
@user = @user_class.new :first_name => 'First', :last_name => 'Last',
|
|
370
|
+
:account_id => 1, :email_address => 'user@foo', :password => 'foobar', :id => 77
|
|
371
|
+
|
|
372
|
+
@api.client.should_receive(:put).with(@api.url_for(@user_class, @user.attributes_for_api),
|
|
373
|
+
KeyTransformer.camelize_keys(@user.attributes_for_api).to_json,
|
|
374
|
+
@api.request_headers).and_return(double(:code => 200, :body => '{}'))
|
|
375
|
+
@api.save(@user)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it "should camelize keys" do
|
|
379
|
+
@res.load(:id => 4, :underscored_key => 'foo')
|
|
380
|
+
@api.client.should_receive(:put).with(anything, {:id => 4, :underscoredKey => 'foo'}.to_json, anything).and_return(
|
|
381
|
+
double(:code => 200, :body => '{}'))
|
|
382
|
+
@api.save(@res)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
context "when attributes_for_api and path_params differ" do
|
|
386
|
+
before do
|
|
387
|
+
@klass = Class.new(Base) do
|
|
388
|
+
self.path = '/test/:special_attr/blah'
|
|
389
|
+
define_schema :id, :special_attr
|
|
390
|
+
end
|
|
391
|
+
@res = @klass.new
|
|
392
|
+
@res.id = 4
|
|
393
|
+
@res.stub!(:attributes).and_return({:id => 4, :special_attr => 'bad' }.with_indifferent_access)
|
|
394
|
+
@res.stub!(:attributes_for_api).and_return({:id => 4, :special_attr => 'bad' }.with_indifferent_access)
|
|
395
|
+
@res.stub!(:path_parameters).and_return({:id => 4, :special_attr => 'good'}.with_indifferent_access)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it "should use the path_parameters for path substitution" do
|
|
399
|
+
FakeWeb.register_uri(:put, %r|/test/good/blah/4$|, :body => '{}')
|
|
400
|
+
#FakeWeb.should have_requested(:put, %r|/test/foo/blah/4$|) # why doesn't this work?
|
|
401
|
+
|
|
402
|
+
@api.save(@res)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it "should use the attributes_for_api for the payload" do
|
|
406
|
+
@api.client.should_receive(:put).with(anything(), {:id => 4, :specialAttr => 'bad'}.to_json, anything()).and_return(
|
|
407
|
+
double(:body => '{}', :code => 200))
|
|
408
|
+
@api.save(@res)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
context "when save succeeds" do
|
|
413
|
+
before do
|
|
414
|
+
FakeWeb.register_uri(:put, %r|.*|, :body => '{"newattr":"newval"}')
|
|
415
|
+
@api.save(@res)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it "should be updated in place" do
|
|
419
|
+
@res.newattr.should == 'newval'
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
context "when host returns a 422" do
|
|
424
|
+
before do
|
|
425
|
+
@res = @klass.new(:id => 4, :attr => 'val')
|
|
426
|
+
FakeWeb.register_uri(:put, %r||, :body => '{"errors":["first"]}', :status => 422)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it "should return false" do
|
|
430
|
+
ret = @api.save(@res)
|
|
431
|
+
ret.should == false
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it "should not modify the resource, except to add errors" do
|
|
435
|
+
old_attrs = @res.attributes.clone
|
|
436
|
+
ret = @api.save(@res)
|
|
437
|
+
@res.attributes.should == old_attrs
|
|
438
|
+
@res.errors.to_hash.should == {:base => ['first']}
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
context "when host returns a 400" do
|
|
443
|
+
before do
|
|
444
|
+
@res = @klass.new(:id => 4, :attr => 'val')
|
|
445
|
+
FakeWeb.register_uri(:put, %r||, :body => '', :status => 400)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
it "should raise an error" do
|
|
449
|
+
expect { @api.save(@res) }.should raise_error RestClient::BadRequest
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
context "when no ID is set" do
|
|
454
|
+
before { @res = @klass.new }
|
|
455
|
+
it "should issue a POST instead of a PUT" do
|
|
456
|
+
FakeWeb.register_uri(:post, %r||, :body => '{}')
|
|
457
|
+
@api.save(@res)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
context "when the resource is invalid" do
|
|
462
|
+
it "should return false" do
|
|
463
|
+
klass = Class.new(Base) do
|
|
464
|
+
self.path = ''
|
|
465
|
+
end
|
|
466
|
+
r = klass.new :id => 1
|
|
467
|
+
r.should_receive('valid?').and_return(false)
|
|
468
|
+
@api.client.stub!(:put).and_return('{}')
|
|
469
|
+
@api.save(r).should == false
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
describe ".delete" do
|
|
475
|
+
context "when called on a class" do
|
|
476
|
+
it "should issue a delete to the specified ID" do
|
|
477
|
+
attrs = { :account_id => 1, :id => 7 }
|
|
478
|
+
FakeWeb.register_uri(:delete, @api.url_for(@user_class, attrs), :status => 200, :body => '')
|
|
479
|
+
@api.delete(@user_class, attrs)
|
|
480
|
+
FakeWeb.should have_requested(:delete, @api.url_for(@user_class, attrs))
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
it "should allow a URL override" do
|
|
484
|
+
FakeWeb.register_uri(:delete, %r|/alternate/delete/path|, :status => 200, :body => '')
|
|
485
|
+
@api.delete(@user_class, '/alternate/delete/path')
|
|
486
|
+
FakeWeb.should have_requested(:delete, %r|/alternate/delete/path|)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
context "when called on a resource" do
|
|
490
|
+
before { @res = @user_class.new(:account_id => 1, :id => 7) }
|
|
491
|
+
|
|
492
|
+
it "should DELETE on the resource's url" do
|
|
493
|
+
FakeWeb.register_uri(:delete, @api.url_for(@user_class, @res.attributes), :status => 200, :body => '')
|
|
494
|
+
@api.delete(@res)
|
|
495
|
+
FakeWeb.should have_requested(:delete, @api.url_for(@user_class, @res.attributes))
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
describe ".get" do
|
|
501
|
+
context "when the response is an XML array" do
|
|
502
|
+
before do
|
|
503
|
+
FakeWeb.register_uri(:get, %r|/array\/resource$|, :body => '<?xml version="1.0" encoding="UTF-8"?><hash></hash>')
|
|
504
|
+
@response = @api.get("#{base_path}/array/resource", XMLFormatter.new)
|
|
505
|
+
end
|
|
506
|
+
it "should return an array" do
|
|
507
|
+
pending
|
|
508
|
+
@response.should be_an Array
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
context "when the response is a json array" do
|
|
513
|
+
before do
|
|
514
|
+
FakeWeb.register_uri(:get, %r|/array\/resource$|, :body => '[{"name":"one"}, {"name":"two"}]')
|
|
515
|
+
@response = @api.get("#{base_path}/array/resource")
|
|
516
|
+
end
|
|
517
|
+
it "should return an array" do
|
|
518
|
+
@response.should be_an Array
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
context "when the response is a hash" do
|
|
523
|
+
before do
|
|
524
|
+
FakeWeb.register_uri(:get, %r|/hash\/resource$|, :body => '{"name":"one"}')
|
|
525
|
+
@response = @api.get("#{base_path}/hash/resource")
|
|
526
|
+
end
|
|
527
|
+
it "should return a hash" do
|
|
528
|
+
@response.should be_a Hash
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# TODO: Make this format-agnostic
|
|
533
|
+
context "when the response isn't valid JSON" do
|
|
534
|
+
before { FakeWeb.register_uri(:get, %r|/invalid/json$|, :body => 'bogus') }
|
|
535
|
+
context "we're expecting json" do
|
|
536
|
+
it "should raise an error" do
|
|
537
|
+
expect { @api.get("#{base_path}/invalid/json") }.should raise_error
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
context "we're not expecting json" do
|
|
541
|
+
it "should return the string" do
|
|
542
|
+
@api.get("#{base_path}/invalid/json", false).should == 'bogus'
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
describe ".post" do
|
|
549
|
+
# TODO: Make this format-agnostic
|
|
550
|
+
context "when parsing JSON" do
|
|
551
|
+
it "should issue a post to the given URL" do
|
|
552
|
+
FakeWeb.register_uri(:post, %r|/custom/post/url|, :body => '{}')
|
|
553
|
+
res = @api.post("#{base_path}/custom/post/url", 'asdf')
|
|
554
|
+
FakeWeb.should have_requested(:post, %r|/custom/post/url|)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
it "should return a hash" do
|
|
558
|
+
FakeWeb.register_uri(:post, %r|/custom/post/url|, :body => '{}')
|
|
559
|
+
res = @api.post("#{base_path}/custom/post/url", 'asdf')
|
|
560
|
+
res.should == {}
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# TODO: Make this format-agnostic
|
|
565
|
+
context "When not parsing JSON" do
|
|
566
|
+
it "should return a string" do
|
|
567
|
+
FakeWeb.register_uri(:post, %r|/custom/post/url|, :body => '{}')
|
|
568
|
+
res = @api.post("#{base_path}/custom/post/url", 'asdf', false)
|
|
569
|
+
res.should == '{}'
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
describe ".put" do
|
|
575
|
+
it "should issue a PUT to the given URL" do
|
|
576
|
+
FakeWeb.register_uri(:put, %r|/custom/put/url|, :body => '{}')
|
|
577
|
+
@api.put("#{base_path}/custom/put/url", [{:a => :b}, {:b => :c}])
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
it "should raise an error if we get a 400" do
|
|
581
|
+
FakeWeb.register_uri(:put, %r|/custom/put/url|, :status => 400, :body => '{}')
|
|
582
|
+
expect { @api.put("#{base_path}/custom/put/url", [{:a => :b}, {:b => :c}]) }.should raise_error RestClient::BadRequest
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
context "when the server returns a list of resources" do
|
|
586
|
+
before { FakeWeb.register_uri(:put, %r|/custom/put/url|, :status => 200, :body => '[{"a":"b"}]') }
|
|
587
|
+
it "should return an array of hashes" do
|
|
588
|
+
ar = @api.put("#{base_path}/custom/put/url", {})
|
|
589
|
+
ar.should be_an Array
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
context "when the server returns a single resource" do
|
|
594
|
+
before { FakeWeb.register_uri(:put, %r|/custom/put/url|, :status => 200, :body => '{"a":"b"}') }
|
|
595
|
+
it "should return an array of hashes" do
|
|
596
|
+
ar = @api.put("#{base_path}/custom/put/url", {})
|
|
597
|
+
ar.should be_a Hash
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
context "when the server returns an empty response" do
|
|
602
|
+
before { FakeWeb.register_uri(:put, %r|/custom/put/url|, :status => 200, :body => '') }
|
|
603
|
+
it "should return an empty string" do
|
|
604
|
+
ar = @api.put("#{base_path}/custom/put/url", {})
|
|
605
|
+
ar.should == ""
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
context "when passed :json => false" do
|
|
610
|
+
before { FakeWeb.register_uri(:put, %r|/custom/put/url|, :status => 200, :body => '{"a":"b"}') }
|
|
611
|
+
it "should return a string" do
|
|
612
|
+
ar = @api.put("#{base_path}/custom/put/url", {}, :json => false)
|
|
613
|
+
ar.should be_a String
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|