cloudkit 0.10.1 → 0.11.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/CHANGES +11 -0
- data/README +7 -6
- data/Rakefile +13 -6
- data/TODO +7 -5
- data/cloudkit.gemspec +23 -23
- data/doc/curl.html +2 -2
- data/doc/index.html +4 -6
- data/examples/5.ru +2 -3
- data/examples/TOC +1 -3
- data/lib/cloudkit.rb +17 -10
- data/lib/cloudkit/constants.rb +0 -6
- data/lib/cloudkit/exceptions.rb +10 -0
- data/lib/cloudkit/flash_session.rb +1 -3
- data/lib/cloudkit/oauth_filter.rb +8 -16
- data/lib/cloudkit/oauth_store.rb +9 -13
- data/lib/cloudkit/openid_filter.rb +25 -7
- data/lib/cloudkit/openid_store.rb +14 -17
- data/lib/cloudkit/request.rb +6 -1
- data/lib/cloudkit/service.rb +15 -15
- data/lib/cloudkit/store.rb +97 -284
- data/lib/cloudkit/store/memory_table.rb +105 -0
- data/lib/cloudkit/store/resource.rb +256 -0
- data/lib/cloudkit/store/response_helpers.rb +0 -1
- data/lib/cloudkit/uri.rb +88 -0
- data/lib/cloudkit/user_store.rb +7 -14
- data/spec/ext_spec.rb +76 -0
- data/spec/flash_session_spec.rb +20 -0
- data/spec/memory_table_spec.rb +86 -0
- data/spec/oauth_filter_spec.rb +326 -0
- data/spec/oauth_store_spec.rb +10 -0
- data/spec/openid_filter_spec.rb +64 -0
- data/spec/openid_store_spec.rb +101 -0
- data/spec/rack_builder_spec.rb +39 -0
- data/spec/request_spec.rb +185 -0
- data/spec/resource_spec.rb +291 -0
- data/spec/service_spec.rb +974 -0
- data/{test/helper.rb → spec/spec_helper.rb} +14 -2
- data/spec/store_spec.rb +10 -0
- data/spec/uri_spec.rb +93 -0
- data/spec/user_store_spec.rb +10 -0
- data/spec/util_spec.rb +11 -0
- metadata +37 -61
- data/examples/6.ru +0 -10
- data/lib/cloudkit/store/adapter.rb +0 -8
- data/lib/cloudkit/store/extraction_view.rb +0 -57
- data/lib/cloudkit/store/sql_adapter.rb +0 -36
- data/test/ext_test.rb +0 -76
- data/test/flash_session_test.rb +0 -22
- data/test/oauth_filter_test.rb +0 -331
- data/test/oauth_store_test.rb +0 -12
- data/test/openid_filter_test.rb +0 -60
- data/test/openid_store_test.rb +0 -12
- data/test/rack_builder_test.rb +0 -41
- data/test/request_test.rb +0 -197
- data/test/service_test.rb +0 -971
- data/test/store_test.rb +0 -93
- data/test/user_store_test.rb +0 -12
- data/test/util_test.rb +0 -13
@@ -0,0 +1,974 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A CloudKit::Service" do
|
4
|
+
|
5
|
+
it "should return a 501 for unimplemented methods" do
|
6
|
+
app = Rack::Builder.new {
|
7
|
+
use Rack::Lint
|
8
|
+
use CloudKit::Service, :collections => [:items, :things]
|
9
|
+
run echo_text('martino')
|
10
|
+
}
|
11
|
+
|
12
|
+
response = Rack::MockRequest.new(app).request('TRACE', '/items')
|
13
|
+
response.status.should == 501
|
14
|
+
|
15
|
+
# disable Rack::Lint so that an invalid HTTP method
|
16
|
+
# can be tested
|
17
|
+
app = Rack::Builder.new {
|
18
|
+
use CloudKit::Service, :collections => [:items, :things]
|
19
|
+
run echo_text('nothing')
|
20
|
+
}
|
21
|
+
response = Rack::MockRequest.new(app).request('REJUXTAPOSE', '/items')
|
22
|
+
response.status.should == 501
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "using auth" do
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
# mock an authenticated service in pieces
|
29
|
+
mock_auth = Proc.new { |env|
|
30
|
+
r = CloudKit::Request.new(env)
|
31
|
+
r.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
32
|
+
}
|
33
|
+
inner_app = echo_text('martino')
|
34
|
+
service = CloudKit::Service.new(
|
35
|
+
inner_app, :collections => [:items, :things])
|
36
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
37
|
+
config = Rack::Config.new(service, &mock_auth)
|
38
|
+
authed_service = Rack::Lint.new(config)
|
39
|
+
@request = Rack::MockRequest.new(authed_service)
|
40
|
+
end
|
41
|
+
|
42
|
+
after(:each) do
|
43
|
+
CloudKit.storage_adapter.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should allow requests for / to pass through" do
|
47
|
+
response = @request.get('/')
|
48
|
+
response.body.should == 'martino'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should allow any non-specified resource request to pass through" do
|
52
|
+
response = @request.get('/hammers')
|
53
|
+
response.body.should == 'martino'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return a 500 if authentication is configured incorrectly" do
|
57
|
+
# simulate auth requirement without CLOUDKIT_AUTH_KEY being set by the
|
58
|
+
# auth filter(s)
|
59
|
+
response = @request.get('/items')
|
60
|
+
response.status.should == 500
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "on GET /cloudkit-meta" do
|
64
|
+
|
65
|
+
before(:each) do
|
66
|
+
@response = @request.get('/cloudkit-meta', VALID_TEST_AUTH)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should be successful" do
|
70
|
+
@response.status.should == 200
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return a list of hosted collection URIs" do
|
74
|
+
uris = JSON.parse(@response.body)['uris']
|
75
|
+
uris.sort.should == ['/items', '/things']
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should return a Content-Type header" do
|
79
|
+
@response['Content-Type'].should == 'application/json'
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return an ETag" do
|
83
|
+
@response['ETag'].should_not be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not set a Last-Modified header" do
|
87
|
+
@response['Last-Modified'].should be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "on GET /:collection" do
|
93
|
+
|
94
|
+
before(:each) do
|
95
|
+
3.times do |i|
|
96
|
+
json = JSON.generate(:this => i.to_s)
|
97
|
+
@request.put("/items/#{i}", {:input => json}.merge(VALID_TEST_AUTH))
|
98
|
+
end
|
99
|
+
json = JSON.generate(:this => '4')
|
100
|
+
@request.put(
|
101
|
+
'/items/4', {:input => json}.merge(CLOUDKIT_AUTH_KEY => 'someoneelse'))
|
102
|
+
@response = @request.get(
|
103
|
+
'/items', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
104
|
+
@parsed_response = JSON.parse(@response.body)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should be successful" do
|
108
|
+
@response.status.should == 200
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return a list of URIs for all owner-originated resources" do
|
112
|
+
@parsed_response['uris'].sort.should == ['/items/0', '/items/1', '/items/2']
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should sort descending on last_modified date" do
|
116
|
+
@parsed_response['uris'].should == ['/items/2', '/items/1', '/items/0']
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return the total number of uris" do
|
120
|
+
@parsed_response['total'].should_not be_nil
|
121
|
+
@parsed_response['total'].should == 3
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should return the offset" do
|
125
|
+
@parsed_response['offset'].should_not be_nil
|
126
|
+
@parsed_response['offset'].should == 0
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should return a Content-Type header" do
|
130
|
+
@response['Content-Type'].should == 'application/json'
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return an ETag" do
|
134
|
+
@response['ETag'].should_not be_nil
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should return a Last-Modified date" do
|
138
|
+
@response['Last-Modified'].should_not be_nil
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should accept a limit parameter" do
|
142
|
+
response = @request.get('/items?limit=2', VALID_TEST_AUTH)
|
143
|
+
parsed_response = JSON.parse(response.body)
|
144
|
+
parsed_response['uris'].should == ['/items/2', '/items/1']
|
145
|
+
parsed_response['total'].should == 3
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should accept an offset parameter" do
|
149
|
+
response = @request.get('/items?offset=1', VALID_TEST_AUTH)
|
150
|
+
parsed_response = JSON.parse(response.body)
|
151
|
+
parsed_response['uris'].should == ['/items/1', '/items/0']
|
152
|
+
parsed_response['offset'].should == 1
|
153
|
+
parsed_response['total'].should == 3
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should accept combined limit and offset parameters" do
|
157
|
+
response = @request.get('/items?limit=1&offset=1', VALID_TEST_AUTH)
|
158
|
+
parsed_response = JSON.parse(response.body)
|
159
|
+
parsed_response['uris'].should == ['/items/1']
|
160
|
+
parsed_response['offset'].should == 1
|
161
|
+
parsed_response['total'].should == 3
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should return an empty list if no resources are found" do
|
165
|
+
response = @request.get('/things', VALID_TEST_AUTH)
|
166
|
+
parsed_response = JSON.parse(response.body)
|
167
|
+
parsed_response['uris'].should == []
|
168
|
+
parsed_response['total'].should == 0
|
169
|
+
parsed_response['offset'].should == 0
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should return a resolved link header" do
|
173
|
+
@response['Link'].should_not be_nil
|
174
|
+
@response['Link'].match("<http://example.org/items/_resolved>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/resolved\"").should_not be_nil
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "on GET /:collection/_resolved" do
|
180
|
+
|
181
|
+
before(:each) do
|
182
|
+
3.times do |i|
|
183
|
+
json = JSON.generate(:this => i.to_s)
|
184
|
+
@request.put("/items/#{i}", {:input => json}.merge(VALID_TEST_AUTH))
|
185
|
+
end
|
186
|
+
json = JSON.generate(:this => '4')
|
187
|
+
@request.put(
|
188
|
+
'/items/4', {:input => json}.merge(CLOUDKIT_AUTH_KEY => 'someoneelse'))
|
189
|
+
@response = @request.get(
|
190
|
+
'/items/_resolved', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
191
|
+
@parsed_response = JSON.parse(@response.body)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should be successful" do
|
195
|
+
@response.status.should == 200
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should return all owner-originated documents" do
|
199
|
+
@parsed_response['documents'].map{|d| d['uri']}.sort.should == ['/items/0', '/items/1', '/items/2']
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should sort descending on last_modified date" do
|
203
|
+
@parsed_response['documents'].map{|d| d['uri']}.should == ['/items/2', '/items/1', '/items/0']
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should return the total number of documents" do
|
207
|
+
@parsed_response['total'].should_not be_nil
|
208
|
+
@parsed_response['total'].should == 3
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should return the offset" do
|
212
|
+
@parsed_response['offset'].should_not be_nil
|
213
|
+
@parsed_response['offset'].should == 0
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should return a Content-Type header" do
|
217
|
+
@response['Content-Type'].should == 'application/json'
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should return an ETag" do
|
221
|
+
@response['ETag'].should_not be_nil
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should return a Last-Modified date" do
|
225
|
+
@response['Last-Modified'].should_not be_nil
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should accept a limit parameter" do
|
229
|
+
response = @request.get('/items/_resolved?limit=2', VALID_TEST_AUTH)
|
230
|
+
parsed_response = JSON.parse(response.body)
|
231
|
+
parsed_response['documents'].map{|d| d['uri']}.should == ['/items/2', '/items/1']
|
232
|
+
parsed_response['total'].should == 3
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should accept an offset parameter" do
|
236
|
+
response = @request.get('/items/_resolved?offset=1', VALID_TEST_AUTH)
|
237
|
+
parsed_response = JSON.parse(response.body)
|
238
|
+
parsed_response['documents'].map{|d| d['uri']}.should == ['/items/1', '/items/0']
|
239
|
+
parsed_response['offset'].should == 1
|
240
|
+
parsed_response['total'].should == 3
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should accept combined limit and offset parameters" do
|
244
|
+
response = @request.get('/items/_resolved?limit=1&offset=1', VALID_TEST_AUTH)
|
245
|
+
parsed_response = JSON.parse(response.body)
|
246
|
+
parsed_response['documents'].map{|d| d['uri']}.should == ['/items/1']
|
247
|
+
parsed_response['offset'].should == 1
|
248
|
+
parsed_response['total'].should == 3
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should return an empty list if no documents are found" do
|
252
|
+
response = @request.get('/things/_resolved', VALID_TEST_AUTH)
|
253
|
+
parsed_response = JSON.parse(response.body)
|
254
|
+
parsed_response['documents'].should == []
|
255
|
+
parsed_response['total'].should == 0
|
256
|
+
parsed_response['offset'].should == 0
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should return an index link header" do
|
260
|
+
@response['Link'].should_not be_nil
|
261
|
+
@response['Link'].match("<http://example.org/items>; rel=\"index\"").should_not be_nil
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "on GET /:collection/:id" do
|
267
|
+
|
268
|
+
before(:each) do
|
269
|
+
json = JSON.generate(:this => 'that')
|
270
|
+
@request.put('/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
271
|
+
@response = @request.get(
|
272
|
+
'/items/abc', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should be successful" do
|
276
|
+
@response.status.should == 200
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should return a document for valid owner-originated requests" do
|
280
|
+
data = JSON.parse(@response.body)
|
281
|
+
data['this'].should == 'that'
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should return a 404 if a document does not exist" do
|
285
|
+
response = @request.get('/items/nothing', VALID_TEST_AUTH)
|
286
|
+
response.status.should == 404
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should return a Content-Type header" do
|
290
|
+
@response['Content-Type'].should == 'application/json'
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should return an ETag header" do
|
294
|
+
@response['ETag'].should_not be_nil
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should return a Last-Modified header" do
|
298
|
+
@response['Last-Modified'].should_not be_nil
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should not return documents for unauthorized users" do
|
302
|
+
response = @request.get('/items/abc', CLOUDKIT_AUTH_KEY => 'bogus')
|
303
|
+
response.status.should == 404
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should return a versions link header" do
|
307
|
+
@response['Link'].should_not be_nil
|
308
|
+
@response['Link'].match("<http://example.org/items/abc/versions>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/versions\"").should_not be_nil
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
describe "on GET /:collection/:id/versions" do
|
314
|
+
|
315
|
+
before(:each) do
|
316
|
+
@etags = []
|
317
|
+
4.times do |i|
|
318
|
+
json = JSON.generate(:this => i)
|
319
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
320
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
321
|
+
result = @request.put('/items/abc', options)
|
322
|
+
@etags << JSON.parse(result.body)['etag']
|
323
|
+
end
|
324
|
+
@response = @request.get(
|
325
|
+
'/items/abc/versions', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
326
|
+
@parsed_response = JSON.parse(@response.body)
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should be successful" do
|
330
|
+
@response.status.should == 200
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should be successful even if the current resource has been deleted" do
|
334
|
+
@request.delete('/items/abc', {'HTTP_IF_MATCH' => @etags.last}.merge(VALID_TEST_AUTH))
|
335
|
+
response = @request.get('/items/abc/versions', VALID_TEST_AUTH)
|
336
|
+
@response.status.should == 200
|
337
|
+
parsed_response = JSON.parse(response.body)
|
338
|
+
parsed_response['uris'].size.should == 4
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should return a list of URIs for all versions of a resource" do
|
342
|
+
uris = @parsed_response['uris']
|
343
|
+
uris.should_not be_nil
|
344
|
+
uris.size.should == 4
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should return a 404 if the resource does not exist" do
|
348
|
+
response = @request.get('/items/nothing/versions', VALID_TEST_AUTH)
|
349
|
+
response.status.should == 404
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should return a 404 for non-owner-originated requests" do
|
353
|
+
response = @request.get(
|
354
|
+
'/items/abc/versions', CLOUDKIT_AUTH_KEY => 'someoneelse')
|
355
|
+
response.status.should == 404
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should sort descending on last_modified date" do
|
359
|
+
@parsed_response['uris'].should == ['/items/abc'].concat(@etags[0..-2].reverse.map{|e| "/items/abc/versions/#{e}"})
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should return the total number of uris" do
|
363
|
+
@parsed_response['total'].should_not be_nil
|
364
|
+
@parsed_response['total'].should == 4
|
365
|
+
end
|
366
|
+
|
367
|
+
it "should return the offset" do
|
368
|
+
@parsed_response['offset'].should_not be_nil
|
369
|
+
@parsed_response['offset'].should == 0
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should return a Content-Type header" do
|
373
|
+
@response['Content-Type'].should == 'application/json'
|
374
|
+
end
|
375
|
+
|
376
|
+
it "should return an ETag" do
|
377
|
+
@response['ETag'].should_not be_nil
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should return a Last-Modified date" do
|
381
|
+
@response['Last-Modified'].should_not be_nil
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should accept a limit parameter" do
|
385
|
+
response = @request.get('/items/abc/versions?limit=2', VALID_TEST_AUTH)
|
386
|
+
parsed_response = JSON.parse(response.body)
|
387
|
+
parsed_response['uris'].should == ['/items/abc', "/items/abc/versions/#{@etags[-2]}"]
|
388
|
+
parsed_response['total'].should == 4
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should accept an offset parameter" do
|
392
|
+
response = @request.get('/items/abc/versions?offset=1', VALID_TEST_AUTH)
|
393
|
+
parsed_response = JSON.parse(response.body)
|
394
|
+
parsed_response['uris'].should == @etags.reverse[1..-1].map{|e| "/items/abc/versions/#{e}"}
|
395
|
+
parsed_response['offset'].should == 1
|
396
|
+
parsed_response['total'].should == 4
|
397
|
+
end
|
398
|
+
|
399
|
+
it "should accept combined limit and offset parameters" do
|
400
|
+
response = @request.get('/items/abc/versions?limit=1&offset=1', VALID_TEST_AUTH)
|
401
|
+
parsed_response = JSON.parse(response.body)
|
402
|
+
parsed_response['uris'].should == ["/items/abc/versions/#{@etags[-2]}"]
|
403
|
+
parsed_response['offset'].should == 1
|
404
|
+
parsed_response['total'].should == 4
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should return a resolved link header" do
|
408
|
+
@response['Link'].should_not be_nil
|
409
|
+
@response['Link'].match("<http://example.org/items/abc/versions/_resolved>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/resolved\"").should_not be_nil
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
413
|
+
|
414
|
+
describe "on GET /:collections/:id/versions/_resolved" do
|
415
|
+
|
416
|
+
before(:each) do
|
417
|
+
@etags = []
|
418
|
+
4.times do |i|
|
419
|
+
json = JSON.generate(:this => i)
|
420
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
421
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
422
|
+
result = @request.put('/items/abc', options)
|
423
|
+
@etags << JSON.parse(result.body)['etag']
|
424
|
+
end
|
425
|
+
@response = @request.get(
|
426
|
+
'/items/abc/versions/_resolved', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
427
|
+
@parsed_response = JSON.parse(@response.body)
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should be successful" do
|
431
|
+
@response.status.should == 200
|
432
|
+
end
|
433
|
+
|
434
|
+
it "should be successful even if the current resource has been deleted" do
|
435
|
+
@request.delete(
|
436
|
+
'/items/abc', {'HTTP_IF_MATCH' => @etags.last}.merge(VALID_TEST_AUTH))
|
437
|
+
response = @request.get('/items/abc/versions/_resolved', VALID_TEST_AUTH)
|
438
|
+
@response.status.should == 200
|
439
|
+
parsed_response = JSON.parse(response.body)
|
440
|
+
parsed_response['documents'].size.should == 4
|
441
|
+
end
|
442
|
+
|
443
|
+
it "should return all versions of a document" do
|
444
|
+
documents = @parsed_response['documents']
|
445
|
+
documents.should_not be_nil
|
446
|
+
documents.size.should == 4
|
447
|
+
end
|
448
|
+
|
449
|
+
it "should return a 404 if the resource does not exist" do
|
450
|
+
response = @request.get('/items/nothing/versions/_resolved', VALID_TEST_AUTH)
|
451
|
+
response.status.should == 404
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should return a 404 for non-owner-originated requests" do
|
455
|
+
response = @request.get('/items/abc/versions/_resolved', CLOUDKIT_AUTH_KEY => 'someoneelse')
|
456
|
+
response.status.should == 404
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should sort descending on last_modified date" do
|
460
|
+
@parsed_response['documents'].map{|d| d['uri']}.should == ['/items/abc'].concat(@etags[0..-2].reverse.map{|e| "/items/abc/versions/#{e}"})
|
461
|
+
end
|
462
|
+
|
463
|
+
it "should return the total number of documents" do
|
464
|
+
@parsed_response['total'].should_not be_nil
|
465
|
+
@parsed_response['total'].should == 4
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should return the offset" do
|
469
|
+
@parsed_response['offset'].should_not be_nil
|
470
|
+
@parsed_response['offset'].should == 0
|
471
|
+
end
|
472
|
+
|
473
|
+
it "should return a Content-Type header" do
|
474
|
+
@response['Content-Type'].should == 'application/json'
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should return an ETag" do
|
478
|
+
@response['ETag'].should_not be_nil
|
479
|
+
end
|
480
|
+
|
481
|
+
it "should return a Last-Modified date" do
|
482
|
+
@response['Last-Modified'].should_not be_nil
|
483
|
+
end
|
484
|
+
|
485
|
+
it "should accept a limit parameter" do
|
486
|
+
response = @request.get(
|
487
|
+
'/items/abc/versions/_resolved?limit=2', VALID_TEST_AUTH)
|
488
|
+
parsed_response = JSON.parse(response.body)
|
489
|
+
parsed_response['documents'].map{|d| d['uri']}.should == ['/items/abc', "/items/abc/versions/#{@etags[-2]}"]
|
490
|
+
parsed_response['total'].should == 4
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should accept an offset parameter" do
|
494
|
+
response = @request.get(
|
495
|
+
'/items/abc/versions/_resolved?offset=1', VALID_TEST_AUTH)
|
496
|
+
parsed_response = JSON.parse(response.body)
|
497
|
+
parsed_response['documents'].map{|d| d['uri']}.should == @etags.reverse[1..-1].map{|e| "/items/abc/versions/#{e}"}
|
498
|
+
parsed_response['offset'].should == 1
|
499
|
+
parsed_response['total'].should == 4
|
500
|
+
end
|
501
|
+
|
502
|
+
it "should accept combined limit and offset parameters" do
|
503
|
+
response = @request.get(
|
504
|
+
'/items/abc/versions/_resolved?limit=1&offset=1', VALID_TEST_AUTH)
|
505
|
+
parsed_response = JSON.parse(response.body)
|
506
|
+
parsed_response['documents'].map{|d| d['uri']}.should == ["/items/abc/versions/#{@etags[-2]}"]
|
507
|
+
parsed_response['offset'].should == 1
|
508
|
+
parsed_response['total'].should == 4
|
509
|
+
end
|
510
|
+
|
511
|
+
it "should return an index link header" do
|
512
|
+
@response['Link'].should_not be_nil
|
513
|
+
@response['Link'].match("<http://example.org/items/abc/versions>; rel=\"index\"").should_not be_nil
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
|
518
|
+
describe "on GET /:collection/:id/versions/:etag" do
|
519
|
+
|
520
|
+
before(:each) do
|
521
|
+
@etags = []
|
522
|
+
2.times do |i|
|
523
|
+
json = JSON.generate(:this => i)
|
524
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
525
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
526
|
+
result = @request.put('/items/abc', options)
|
527
|
+
@etags << JSON.parse(result.body)['etag']
|
528
|
+
end
|
529
|
+
@response = @request.get(
|
530
|
+
"/items/abc/versions/#{@etags.first}", VALID_TEST_AUTH)
|
531
|
+
@parsed_response = JSON.parse(@response.body)
|
532
|
+
end
|
533
|
+
|
534
|
+
it "should be successful" do
|
535
|
+
@response.status.should == 200
|
536
|
+
end
|
537
|
+
|
538
|
+
it "should return a document for valid owner-originated requests" do
|
539
|
+
@parsed_response['this'].should == 0
|
540
|
+
end
|
541
|
+
|
542
|
+
it "should return a 404 if a document is not found" do
|
543
|
+
response = @request.get(
|
544
|
+
"/items/nothing/versions/#{@etags.first}", VALID_TEST_AUTH)
|
545
|
+
response.status.should == 404
|
546
|
+
end
|
547
|
+
|
548
|
+
it "should return a Content-Type header" do
|
549
|
+
@response['Content-Type'].should == 'application/json'
|
550
|
+
end
|
551
|
+
|
552
|
+
it "should return an ETag header" do
|
553
|
+
@response['ETag'].should_not be_nil
|
554
|
+
end
|
555
|
+
|
556
|
+
it "should return a Last-Modified header" do
|
557
|
+
@response['Last-Modified'].should_not be_nil
|
558
|
+
end
|
559
|
+
|
560
|
+
it "should not return documents for unauthorized users" do
|
561
|
+
response = @request.get(
|
562
|
+
"/items/abc/versions/#{@etags.first}", CLOUDKIT_AUTH_KEY => 'someoneelse')
|
563
|
+
response.status.should == 404
|
564
|
+
end
|
565
|
+
|
566
|
+
end
|
567
|
+
|
568
|
+
describe "on POST /:collection" do
|
569
|
+
|
570
|
+
before(:each) do
|
571
|
+
json = JSON.generate(:this => 'that')
|
572
|
+
@response = @request.post(
|
573
|
+
'/items', {:input => json}.merge(VALID_TEST_AUTH))
|
574
|
+
@body = JSON.parse(@response.body)
|
575
|
+
end
|
576
|
+
|
577
|
+
it "should store the document" do
|
578
|
+
result = @request.get(@body['uri'], VALID_TEST_AUTH)
|
579
|
+
result.status.should == 200
|
580
|
+
end
|
581
|
+
|
582
|
+
it "should return a 201 when successful" do
|
583
|
+
@response.status.should == 201
|
584
|
+
end
|
585
|
+
|
586
|
+
it "should return the metadata" do
|
587
|
+
@body.keys.size.should == 4
|
588
|
+
@body.keys.sort.should == ['etag', 'last_modified', 'ok', 'uri']
|
589
|
+
end
|
590
|
+
|
591
|
+
it "should set the Content-Type header" do
|
592
|
+
@response['Content-Type'].should == 'application/json'
|
593
|
+
end
|
594
|
+
|
595
|
+
it "should not set an ETag header" do
|
596
|
+
@response['ETag'].should be_nil
|
597
|
+
end
|
598
|
+
|
599
|
+
it "should not set a Last-Modified header" do
|
600
|
+
@response['Last-Modified'].should be_nil
|
601
|
+
end
|
602
|
+
|
603
|
+
it "should return a 422 if parsing fails" do
|
604
|
+
response = @request.post('/items', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
605
|
+
response.status.should == 422
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|
609
|
+
|
610
|
+
describe "on PUT /:collection/:id" do
|
611
|
+
|
612
|
+
before(:each) do
|
613
|
+
json = JSON.generate(:this => 'that')
|
614
|
+
@original = @request.put(
|
615
|
+
'/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
616
|
+
etag = JSON.parse(@original.body)['etag']
|
617
|
+
json = JSON.generate(:this => 'other')
|
618
|
+
@response = @request.put(
|
619
|
+
'/items/abc',
|
620
|
+
:input => json,
|
621
|
+
'HTTP_IF_MATCH' => etag,
|
622
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
623
|
+
@json = JSON.parse(@response.body)
|
624
|
+
end
|
625
|
+
|
626
|
+
it "should create a document if it does not already exist" do
|
627
|
+
json = JSON.generate(:this => 'thing')
|
628
|
+
response = @request.put(
|
629
|
+
'/items/xyz', {:input => json}.merge(VALID_TEST_AUTH))
|
630
|
+
response.status.should == 201
|
631
|
+
result = @request.get('/items/xyz', VALID_TEST_AUTH)
|
632
|
+
result.status.should == 200
|
633
|
+
JSON.parse(result.body)['this'].should == 'thing'
|
634
|
+
end
|
635
|
+
|
636
|
+
it "should not create new resources using deleted resource URIs" do
|
637
|
+
# This situation occurs when a stale client attempts to update
|
638
|
+
# a resource that has been removed. This test verifies that CloudKit
|
639
|
+
# does not attempt to create a new item with a URI equal to the
|
640
|
+
# removed item.
|
641
|
+
etag = JSON.parse(@response.body)['etag'];
|
642
|
+
@request.delete(
|
643
|
+
'/items/abc',
|
644
|
+
'HTTP_IF_MATCH' => etag,
|
645
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
646
|
+
json = JSON.generate(:foo => 'bar')
|
647
|
+
response = @request.put(
|
648
|
+
'/items/abc',
|
649
|
+
:input => json,
|
650
|
+
'HTTP_IF_MATCH' => etag,
|
651
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
652
|
+
response.status.should == 410
|
653
|
+
end
|
654
|
+
|
655
|
+
it "should update the document if it already exists" do
|
656
|
+
@response.status.should == 200
|
657
|
+
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
658
|
+
JSON.parse(result.body)['this'].should == 'other'
|
659
|
+
end
|
660
|
+
|
661
|
+
it "should return the metadata" do
|
662
|
+
@json.keys.size.should == 4
|
663
|
+
@json.keys.sort.should == ['etag', 'last_modified', 'ok', 'uri']
|
664
|
+
end
|
665
|
+
|
666
|
+
it "should set the Content-Type header" do
|
667
|
+
@response['Content-Type'].should == 'application/json'
|
668
|
+
end
|
669
|
+
|
670
|
+
it "should not set an ETag header" do
|
671
|
+
@response['ETag'].should be_nil
|
672
|
+
end
|
673
|
+
|
674
|
+
it "should not set a Last-Modified header" do
|
675
|
+
@response['Last-Modified'].should be_nil
|
676
|
+
end
|
677
|
+
|
678
|
+
it "should not allow a remote_user change" do
|
679
|
+
json = JSON.generate(:this => 'other')
|
680
|
+
response = @request.put(
|
681
|
+
'/items/abc',
|
682
|
+
:input => json,
|
683
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
684
|
+
CLOUDKIT_AUTH_KEY => 'someone_else')
|
685
|
+
response.status.should == 404
|
686
|
+
end
|
687
|
+
|
688
|
+
it "should detect and return conflicts" do
|
689
|
+
client_a_input = JSON.generate(:this => 'updated')
|
690
|
+
client_b_input = JSON.generate(:other => 'thing')
|
691
|
+
response = @request.put(
|
692
|
+
'/items/abc',
|
693
|
+
:input => client_a_input,
|
694
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
695
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
696
|
+
response.status.should == 200
|
697
|
+
response = @request.put(
|
698
|
+
'/items/abc',
|
699
|
+
:input => client_b_input,
|
700
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
701
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
702
|
+
response.status.should == 412
|
703
|
+
end
|
704
|
+
|
705
|
+
it "should require an ETag for updates" do
|
706
|
+
json = JSON.generate(:this => 'updated')
|
707
|
+
response = @request.put(
|
708
|
+
'/items/abc',
|
709
|
+
{:input => json}.merge(VALID_TEST_AUTH))
|
710
|
+
response.status.should == 400
|
711
|
+
end
|
712
|
+
|
713
|
+
it "should return a 422 if parsing fails" do
|
714
|
+
response = @request.put(
|
715
|
+
'/items/zzz', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
716
|
+
response.status.should == 422
|
717
|
+
end
|
718
|
+
|
719
|
+
it "should version document updates" do
|
720
|
+
json = JSON.generate(:this => 'updated')
|
721
|
+
response = @request.put(
|
722
|
+
'/items/abc',
|
723
|
+
:input => json,
|
724
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
725
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
726
|
+
response.status.should == 200
|
727
|
+
etag = JSON.parse(response.body)['etag']
|
728
|
+
json = JSON.generate(:this => 'updated again')
|
729
|
+
new_response = @request.put(
|
730
|
+
'/items/abc',
|
731
|
+
:input => json,
|
732
|
+
'HTTP_IF_MATCH' => etag,
|
733
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
734
|
+
new_response.status.should == 200
|
735
|
+
new_etag = JSON.parse(new_response.body)['etag']
|
736
|
+
new_etag.should_not == etag
|
737
|
+
end
|
738
|
+
|
739
|
+
end
|
740
|
+
|
741
|
+
describe "on DELETE /:collection/:id" do
|
742
|
+
|
743
|
+
before(:each) do
|
744
|
+
json = JSON.generate(:this => 'that')
|
745
|
+
@result = @request.put(
|
746
|
+
'/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
747
|
+
@etag = JSON.parse(@result.body)['etag']
|
748
|
+
end
|
749
|
+
|
750
|
+
it "should delete the document" do
|
751
|
+
response = @request.delete(
|
752
|
+
'/items/abc',
|
753
|
+
'HTTP_IF_MATCH' => @etag,
|
754
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
755
|
+
response.status.should == 200
|
756
|
+
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
757
|
+
result.status.should == 410
|
758
|
+
end
|
759
|
+
|
760
|
+
it "should return the metadata" do
|
761
|
+
response = @request.delete(
|
762
|
+
'/items/abc',
|
763
|
+
'HTTP_IF_MATCH' => @etag,
|
764
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
765
|
+
json = JSON.parse(response.body)
|
766
|
+
json.keys.size.should == 4
|
767
|
+
json.keys.sort.should == ['etag', 'last_modified', 'ok', 'uri']
|
768
|
+
end
|
769
|
+
|
770
|
+
it "should set the Content-Type header" do
|
771
|
+
response = @request.delete(
|
772
|
+
'/items/abc',
|
773
|
+
'HTTP_IF_MATCH' => @etag,
|
774
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
775
|
+
response['Content-Type'].should == 'application/json'
|
776
|
+
end
|
777
|
+
|
778
|
+
it "should not set an ETag header" do
|
779
|
+
response = @request.delete(
|
780
|
+
'/items/abc',
|
781
|
+
'HTTP_IF_MATCH' => @etag,
|
782
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
783
|
+
response['ETag'].should be_nil
|
784
|
+
end
|
785
|
+
|
786
|
+
it "should not set a Last-Modified header" do
|
787
|
+
response = @request.delete(
|
788
|
+
'/items/abc',
|
789
|
+
'HTTP_IF_MATCH' => @etag,
|
790
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
791
|
+
response['Last-Modified'].should be_nil
|
792
|
+
end
|
793
|
+
|
794
|
+
it "should return a 404 for items that have never existed" do
|
795
|
+
response = @request.delete(
|
796
|
+
'/items/zzz',
|
797
|
+
'HTTP_IF_MATCH' => @etag,
|
798
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
799
|
+
response.status.should == 404
|
800
|
+
end
|
801
|
+
|
802
|
+
it "should require an ETag" do
|
803
|
+
response = @request.delete(
|
804
|
+
'/items/abc',
|
805
|
+
VALID_TEST_AUTH)
|
806
|
+
response.status.should == 400
|
807
|
+
end
|
808
|
+
|
809
|
+
it "should verify the user in the doc" do
|
810
|
+
response = @request.delete(
|
811
|
+
'/items/abc',
|
812
|
+
'HTTP_IF_MATCH' => @etag,
|
813
|
+
CLOUDKIT_AUTH_KEY => 'someoneelse')
|
814
|
+
response.status.should == 404
|
815
|
+
end
|
816
|
+
|
817
|
+
it "should detect and return conflicts" do
|
818
|
+
json = JSON.generate(:this => 'that')
|
819
|
+
result = @request.put(
|
820
|
+
'/items/123', {:input => json}.merge(VALID_TEST_AUTH))
|
821
|
+
etag = JSON.parse(result.body)['etag']
|
822
|
+
client_a_input = JSON.generate(:this => 'updated')
|
823
|
+
client_b_input = JSON.generate(:other => 'thing')
|
824
|
+
response = @request.put(
|
825
|
+
'/items/123',
|
826
|
+
:input => client_a_input,
|
827
|
+
'HTTP_IF_MATCH' => etag,
|
828
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
829
|
+
response.status.should == 200
|
830
|
+
response = @request.delete(
|
831
|
+
'/items/123',
|
832
|
+
:input => client_b_input,
|
833
|
+
'HTTP_IF_MATCH' => etag,
|
834
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
835
|
+
response.status.should == 412
|
836
|
+
end
|
837
|
+
|
838
|
+
it "should retain version history" do
|
839
|
+
response = @request.delete(
|
840
|
+
'/items/abc',
|
841
|
+
'HTTP_IF_MATCH' => @etag,
|
842
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
843
|
+
response.status.should == 200
|
844
|
+
response = @request.get(
|
845
|
+
'/items/abc/versions',
|
846
|
+
VALID_TEST_AUTH)
|
847
|
+
json = JSON.parse(response.body)
|
848
|
+
json['total'].should == 1
|
849
|
+
end
|
850
|
+
|
851
|
+
end
|
852
|
+
|
853
|
+
describe "on OPTIONS /:collection" do
|
854
|
+
|
855
|
+
before(:each) do
|
856
|
+
@response = @request.request('OPTIONS', '/items', VALID_TEST_AUTH)
|
857
|
+
end
|
858
|
+
|
859
|
+
it "should return a 200 status" do
|
860
|
+
@response.status.should == 200
|
861
|
+
end
|
862
|
+
|
863
|
+
it "should return a list of available methods" do
|
864
|
+
@response['Allow'].should_not be_nil
|
865
|
+
methods = @response['Allow'].split(', ')
|
866
|
+
methods.sort.should == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
867
|
+
end
|
868
|
+
|
869
|
+
end
|
870
|
+
|
871
|
+
describe "on OPTIONS /:collection/_resolved" do
|
872
|
+
|
873
|
+
before(:each) do
|
874
|
+
@response = @request.request('OPTIONS', '/items/_resolved', VALID_TEST_AUTH)
|
875
|
+
end
|
876
|
+
|
877
|
+
it "should return a 200 status" do
|
878
|
+
@response.status.should == 200
|
879
|
+
end
|
880
|
+
|
881
|
+
it "should return a list of available methods" do
|
882
|
+
@response['Allow'].should_not be_nil
|
883
|
+
methods = @response['Allow'].split(', ')
|
884
|
+
methods.sort.should == ['GET', 'HEAD', 'OPTIONS']
|
885
|
+
end
|
886
|
+
|
887
|
+
end
|
888
|
+
|
889
|
+
describe "on OPTIONS /:collection/:id" do
|
890
|
+
|
891
|
+
before(:each) do
|
892
|
+
@response = @request.request('OPTIONS', '/items/xyz', VALID_TEST_AUTH)
|
893
|
+
end
|
894
|
+
|
895
|
+
it "should return a 200 status" do
|
896
|
+
@response.status.should == 200
|
897
|
+
end
|
898
|
+
|
899
|
+
it "should return a list of available methods" do
|
900
|
+
@response['Allow'].should_not be_nil
|
901
|
+
methods = @response['Allow'].split(', ')
|
902
|
+
methods.sort.should == ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT']
|
903
|
+
end
|
904
|
+
|
905
|
+
end
|
906
|
+
|
907
|
+
describe "on OPTIONS /:collection/:id/versions" do
|
908
|
+
|
909
|
+
before(:each) do
|
910
|
+
@response = @request.request('OPTIONS', '/items/xyz/versions', VALID_TEST_AUTH)
|
911
|
+
end
|
912
|
+
|
913
|
+
it "should return a 200 status" do
|
914
|
+
@response.status.should == 200
|
915
|
+
end
|
916
|
+
|
917
|
+
it "should return a list of available methods" do
|
918
|
+
@response['Allow'].should_not be_nil
|
919
|
+
methods = @response['Allow'].split(', ')
|
920
|
+
methods.sort.should == ['GET', 'HEAD', 'OPTIONS']
|
921
|
+
end
|
922
|
+
|
923
|
+
end
|
924
|
+
|
925
|
+
describe "on OPTIONS /:collection/:id/versions/_resolved" do
|
926
|
+
|
927
|
+
before(:each) do
|
928
|
+
@response = @request.request('OPTIONS', '/items/xyz/versions/_resolved', VALID_TEST_AUTH)
|
929
|
+
end
|
930
|
+
|
931
|
+
it "should return a 200 status" do
|
932
|
+
@response.status.should == 200
|
933
|
+
end
|
934
|
+
|
935
|
+
it "should return a list of available methods" do
|
936
|
+
@response['Allow'].should_not be_nil
|
937
|
+
methods = @response['Allow'].split(', ')
|
938
|
+
methods.sort.should == ['GET', 'HEAD', 'OPTIONS']
|
939
|
+
end
|
940
|
+
|
941
|
+
end
|
942
|
+
|
943
|
+
describe "on OPTIONS /:collection/:id/versions/:etag" do
|
944
|
+
|
945
|
+
before(:each) do
|
946
|
+
@response = @request.request('OPTIONS', "/items/xyz/versions/abc", VALID_TEST_AUTH)
|
947
|
+
end
|
948
|
+
|
949
|
+
it "should return a 200 status" do
|
950
|
+
@response.status.should == 200
|
951
|
+
end
|
952
|
+
|
953
|
+
it "should return a list of available methods" do
|
954
|
+
@response['Allow'].should_not be_nil
|
955
|
+
methods = @response['Allow'].split(', ')
|
956
|
+
methods.sort.should == ['GET', 'HEAD', 'OPTIONS']
|
957
|
+
end
|
958
|
+
|
959
|
+
end
|
960
|
+
|
961
|
+
describe "on HEAD" do
|
962
|
+
|
963
|
+
it "should return an empty body" do
|
964
|
+
json = JSON.generate(:this => 'that')
|
965
|
+
@request.put('/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
966
|
+
response = @request.request('HEAD', '/items/abc', VALID_TEST_AUTH)
|
967
|
+
response.body.should == ''
|
968
|
+
response = @request.request('HEAD', '/items', VALID_TEST_AUTH)
|
969
|
+
response.body.should == ''
|
970
|
+
end
|
971
|
+
|
972
|
+
end
|
973
|
+
end
|
974
|
+
end
|