cloudkit 0.9.1 → 0.10.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 +6 -0
- data/COPYING +1 -1
- data/README +7 -7
- data/TODO +4 -3
- data/cloudkit.gemspec +4 -4
- data/doc/curl.html +133 -72
- data/doc/index.html +1 -1
- data/doc/rest-api.html +109 -0
- data/examples/3.ru +1 -1
- data/examples/4.ru +1 -1
- data/examples/5.ru +2 -2
- data/examples/6.ru +2 -2
- data/lib/cloudkit.rb +7 -1
- data/lib/cloudkit/constants.rb +40 -0
- data/lib/cloudkit/oauth_filter.rb +9 -9
- data/lib/cloudkit/oauth_store.rb +1 -5
- data/lib/cloudkit/openid_filter.rb +11 -11
- data/lib/cloudkit/rack/builder.rb +3 -3
- data/lib/cloudkit/request.rb +18 -12
- data/lib/cloudkit/service.rb +26 -8
- data/lib/cloudkit/store.rb +109 -34
- data/lib/cloudkit/store/adapter.rb +0 -1
- data/lib/cloudkit/store/response.rb +1 -0
- data/lib/cloudkit/store/sql_adapter.rb +2 -2
- data/lib/cloudkit/util.rb +2 -37
- data/test/helper.rb +6 -39
- data/test/oauth_filter_test.rb +13 -13
- data/test/openid_filter_test.rb +14 -8
- data/test/request_test.rb +6 -6
- data/test/service_test.rb +337 -113
- data/test/store_test.rb +4 -10
- metadata +4 -4
- data/lib/cloudkit/rack/lint.rb +0 -12
data/test/helper.rb
CHANGED
@@ -4,50 +4,17 @@ require 'test/unit'
|
|
4
4
|
require 'shoulda'
|
5
5
|
require 'rexml/document'
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
TEST_REMOTE_USER = '/cloudkit_users/abcdef'.freeze
|
8
|
+
VALID_TEST_AUTH = {CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER}.freeze
|
9
9
|
|
10
10
|
def echo_text(text)
|
11
|
-
lambda {|env| [200,
|
11
|
+
lambda {|env| [200, app_headers(text), [text]]}
|
12
12
|
end
|
13
13
|
|
14
14
|
def echo_env(key)
|
15
|
-
lambda {|env| [200,
|
15
|
+
lambda {|env| [200, app_headers(env[key] || ''), [env[key] || '']]}
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
{
|
20
|
-
end
|
21
|
-
|
22
|
-
def plain_service
|
23
|
-
Rack::Builder.new do
|
24
|
-
use Rack::Lint
|
25
|
-
use Rack::Config do |env|
|
26
|
-
env['cloudkit.storage.uri'] = 'sqlite://service.db'
|
27
|
-
end
|
28
|
-
use CloudKit::Service, :collections => [:items, :things]
|
29
|
-
run echo_text('martino')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def authed_service
|
34
|
-
Rack::Builder.new do
|
35
|
-
use Rack::Lint
|
36
|
-
use Rack::Config do |env|
|
37
|
-
env['cloudkit.storage.uri'] = 'sqlite://service.db'
|
38
|
-
r = CloudKit::Request.new(env)
|
39
|
-
r.announce_auth('cloudkit.filter.oauth') # mock
|
40
|
-
end
|
41
|
-
use CloudKit::Service, :collections => [:items, :things]
|
42
|
-
run echo_text('martino')
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def openid_app
|
47
|
-
Rack::Builder.new do
|
48
|
-
use Rack::Lint
|
49
|
-
use Rack::Session::Pool
|
50
|
-
use CloudKit::OpenIDFilter
|
51
|
-
run echo_env(auth_key)
|
52
|
-
end
|
18
|
+
def app_headers(content)
|
19
|
+
{'Content-Type' => 'text/html', 'Content-Length' => content.length.to_s}
|
53
20
|
end
|
data/test/oauth_filter_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
2
|
class OAuthFilterTest < Test::Unit::TestCase
|
3
3
|
|
4
|
-
context "An OAuthFilter" do
|
4
|
+
context "An OAuthFilter" do
|
5
5
|
|
6
6
|
setup do
|
7
|
-
@oauth_filtered_app = CloudKit::OAuthFilter.new(echo_env(
|
7
|
+
@oauth_filtered_app = CloudKit::OAuthFilter.new(echo_env(CLOUDKIT_AUTH_KEY))
|
8
8
|
token = JSON.generate(
|
9
9
|
:secret => 'pfkkdhi9sl3r4s00',
|
10
10
|
:consumer_key => 'dpf43f3p2l4k3l03',
|
@@ -28,9 +28,9 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
28
28
|
end
|
29
29
|
|
30
30
|
should "notify downstream nodes of its presence" do
|
31
|
-
app = CloudKit::OAuthFilter.new(echo_env(
|
31
|
+
app = CloudKit::OAuthFilter.new(echo_env(CLOUDKIT_VIA))
|
32
32
|
response = Rack::MockRequest.new(app).get('/')
|
33
|
-
assert_equal
|
33
|
+
assert_equal CLOUDKIT_OAUTH_FILTER_KEY, response.body
|
34
34
|
end
|
35
35
|
|
36
36
|
should "not allow a nonce/timestamp combination to appear twice" do
|
@@ -69,12 +69,12 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
69
69
|
|
70
70
|
should "set the auth challenge for unauthorized requests" do
|
71
71
|
app = CloudKit::OAuthFilter.new(
|
72
|
-
lambda {|env| [200, {}, [env[
|
72
|
+
lambda {|env| [200, {}, [env[CLOUDKIT_AUTH_CHALLENGE]['WWW-Authenticate'] || '']]})
|
73
73
|
response = Rack::MockRequest.new(app).get(
|
74
74
|
'/items', 'HTTP_HOST' => 'example.org')
|
75
75
|
assert_equal 'OAuth realm="http://example.org"', response.body
|
76
76
|
app = CloudKit::OAuthFilter.new(
|
77
|
-
lambda {|env| [200, {}, [env[
|
77
|
+
lambda {|env| [200, {}, [env[CLOUDKIT_AUTH_CHALLENGE]['Link'] || '']]})
|
78
78
|
response = Rack::MockRequest.new(app).get(
|
79
79
|
'/items', 'HTTP_HOST' => 'example.org')
|
80
80
|
assert_equal '<http://example.org/oauth/meta>; rel="http://oauth.net/discovery/1.0/rel/provider"',
|
@@ -186,7 +186,7 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
186
186
|
response = get_request_token
|
187
187
|
token, secret = extract_token(response)
|
188
188
|
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
189
|
-
"/oauth/authorization?oauth_token=#{token}",
|
189
|
+
"/oauth/authorization?oauth_token=#{token}", VALID_TEST_AUTH)
|
190
190
|
assert_equal 200, response.status
|
191
191
|
end
|
192
192
|
|
@@ -194,7 +194,7 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
194
194
|
response = get_request_token
|
195
195
|
token, secret = extract_token(response)
|
196
196
|
response = Rack::MockRequest.new(@oauth_filtered_app).get(
|
197
|
-
"/oauth/authorization?oauth_token=fail",
|
197
|
+
"/oauth/authorization?oauth_token=fail", VALID_TEST_AUTH)
|
198
198
|
assert_equal 401, response.status
|
199
199
|
end
|
200
200
|
|
@@ -202,7 +202,7 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
202
202
|
response = get_request_token
|
203
203
|
token, secret = extract_token(response)
|
204
204
|
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
205
|
-
"/oauth/authorized_request_tokens/#{token}?submit=Approve",
|
205
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
206
206
|
assert_equal 200, response.status
|
207
207
|
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
208
208
|
assert request_token['authorized_at']
|
@@ -213,7 +213,7 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
213
213
|
response = get_request_token
|
214
214
|
token, secret = extract_token(response)
|
215
215
|
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
216
|
-
"/oauth/authorized_request_tokens/#{token}?submit=Deny",
|
216
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Deny", VALID_TEST_AUTH)
|
217
217
|
assert_equal 200, response.status
|
218
218
|
request_token = @store.get("/cloudkit_oauth_request_tokens/#{token}").parsed_content
|
219
219
|
assert 410, response.status
|
@@ -232,10 +232,10 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
232
232
|
response = get_request_token
|
233
233
|
token, secret = extract_token(response)
|
234
234
|
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
235
|
-
"/oauth/authorized_request_tokens/#{token}?submit=Approve",
|
235
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
236
236
|
assert_equal 200, response.status
|
237
237
|
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
238
|
-
"/oauth/authorized_request_tokens/#{token}?submit=Approve",
|
238
|
+
"/oauth/authorized_request_tokens/#{token}?submit=Approve", VALID_TEST_AUTH)
|
239
239
|
assert_equal 401, response.status
|
240
240
|
end
|
241
241
|
|
@@ -296,7 +296,7 @@ class OAuthFilterTest < Test::Unit::TestCase
|
|
296
296
|
response = get_request_token
|
297
297
|
token, secret = extract_token(response)
|
298
298
|
response = Rack::MockRequest.new(@oauth_filtered_app).put(
|
299
|
-
"/oauth/authorized_request_tokens/#{token}",
|
299
|
+
"/oauth/authorized_request_tokens/#{token}", VALID_TEST_AUTH)
|
300
300
|
assert_equal 200, response.status
|
301
301
|
pre_sign = Rack::Request.new(Rack::MockRequest.env_for(
|
302
302
|
'http://photos.example.net/oauth/access_tokens',
|
data/test/openid_filter_test.rb
CHANGED
@@ -4,6 +4,12 @@ class OpenIDFilterTest < Test::Unit::TestCase
|
|
4
4
|
context "An OpenIDFilter" do
|
5
5
|
|
6
6
|
setup do
|
7
|
+
openid_app = Rack::Builder.new {
|
8
|
+
use Rack::Lint
|
9
|
+
use Rack::Session::Pool
|
10
|
+
use CloudKit::OpenIDFilter
|
11
|
+
run echo_env(CLOUDKIT_AUTH_KEY)
|
12
|
+
}
|
7
13
|
@request = Rack::MockRequest.new(openid_app)
|
8
14
|
end
|
9
15
|
|
@@ -22,31 +28,31 @@ class OpenIDFilterTest < Test::Unit::TestCase
|
|
22
28
|
app = Rack::Builder.new do
|
23
29
|
use Rack::Session::Pool
|
24
30
|
use CloudKit::OpenIDFilter
|
25
|
-
run echo_env(
|
31
|
+
run echo_env(CLOUDKIT_VIA)
|
26
32
|
end
|
27
33
|
response = Rack::MockRequest.new(app).get('/')
|
28
|
-
assert_equal
|
34
|
+
assert_equal CLOUDKIT_OPENID_FILTER_KEY, response.body
|
29
35
|
end
|
30
36
|
|
31
37
|
context "with upstream authorization middleware" do
|
32
38
|
|
33
39
|
should "allow pass through if the auth env variable is populated" do
|
34
|
-
response = @request.get('/protected',
|
40
|
+
response = @request.get('/protected', VALID_TEST_AUTH)
|
35
41
|
assert_equal 200, response.status
|
36
|
-
assert_equal
|
42
|
+
assert_equal TEST_REMOTE_USER, response.body
|
37
43
|
end
|
38
44
|
|
39
45
|
should "return the auth challenge header" do
|
40
46
|
response = @request.get('/protected',
|
41
|
-
|
42
|
-
|
47
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
48
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
43
49
|
assert response['WWW-Authenticate']
|
44
50
|
end
|
45
51
|
|
46
52
|
should "return a 401 status if authorization is required" do
|
47
53
|
response = @request.get('/protected',
|
48
|
-
|
49
|
-
|
54
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
55
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
50
56
|
assert_equal 401, response.status
|
51
57
|
end
|
52
58
|
end
|
data/test/request_test.rb
CHANGED
@@ -68,13 +68,13 @@ class RequestTest < Test::Unit::TestCase
|
|
68
68
|
|
69
69
|
should "announce the use of auth middleware" do
|
70
70
|
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
71
|
-
request.announce_auth(
|
72
|
-
assert request.via.include?(
|
71
|
+
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
72
|
+
assert request.via.include?(CLOUDKIT_OAUTH_FILTER_KEY)
|
73
73
|
end
|
74
74
|
|
75
75
|
should "know if auth provided by upstream middleware" do
|
76
76
|
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
77
|
-
request.announce_auth(
|
77
|
+
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
78
78
|
assert request.using_auth?
|
79
79
|
end
|
80
80
|
|
@@ -82,7 +82,7 @@ class RequestTest < Test::Unit::TestCase
|
|
82
82
|
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
83
83
|
assert_nil request.current_user
|
84
84
|
request = CloudKit::Request.new(
|
85
|
-
Rack::MockRequest.env_for('/',
|
85
|
+
Rack::MockRequest.env_for('/', CLOUDKIT_AUTH_KEY => 'cecil'))
|
86
86
|
assert request.current_user
|
87
87
|
assert_equal 'cecil', request.current_user
|
88
88
|
end
|
@@ -99,7 +99,7 @@ class RequestTest < Test::Unit::TestCase
|
|
99
99
|
assert_equal '/login', request.login_url
|
100
100
|
request = CloudKit::Request.new(
|
101
101
|
Rack::MockRequest.env_for(
|
102
|
-
'/',
|
102
|
+
'/', CLOUDKIT_LOGIN_URL => '/sessions'))
|
103
103
|
assert_equal '/sessions', request.login_url
|
104
104
|
end
|
105
105
|
|
@@ -114,7 +114,7 @@ class RequestTest < Test::Unit::TestCase
|
|
114
114
|
assert_equal '/logout', request.logout_url
|
115
115
|
request = CloudKit::Request.new(
|
116
116
|
Rack::MockRequest.env_for(
|
117
|
-
'/',
|
117
|
+
'/', CLOUDKIT_LOGOUT_URL => '/sessions'))
|
118
118
|
assert_equal '/sessions', request.logout_url
|
119
119
|
end
|
120
120
|
|
data/test/service_test.rb
CHANGED
@@ -3,24 +3,19 @@ class ServiceTest < Test::Unit::TestCase
|
|
3
3
|
|
4
4
|
context "A CloudKit::Service" do
|
5
5
|
|
6
|
-
setup do
|
7
|
-
@request = Rack::MockRequest.new(plain_service)
|
8
|
-
end
|
9
|
-
|
10
|
-
teardown do
|
11
|
-
FileUtils.rm_f('service.db')
|
12
|
-
end
|
13
|
-
|
14
6
|
should "return a 501 for unimplemented methods" do
|
15
|
-
|
7
|
+
app = Rack::Builder.new {
|
8
|
+
use Rack::Lint
|
9
|
+
use CloudKit::Service, :collections => [:items, :things]
|
10
|
+
run echo_text('martino')
|
11
|
+
}
|
12
|
+
|
13
|
+
response = Rack::MockRequest.new(app).request('TRACE', '/items')
|
16
14
|
assert_equal 501, response.status
|
17
15
|
|
18
16
|
# disable Rack::Lint so that an invalid HTTP method
|
19
17
|
# can be tested
|
20
18
|
app = Rack::Builder.new {
|
21
|
-
use Rack::Config do |env|
|
22
|
-
env['cloudkit.storage.uri'] = 'sqlite://service.db'
|
23
|
-
end
|
24
19
|
use CloudKit::Service, :collections => [:items, :things]
|
25
20
|
run echo_text('nothing')
|
26
21
|
}
|
@@ -31,9 +26,16 @@ class ServiceTest < Test::Unit::TestCase
|
|
31
26
|
context "using auth" do
|
32
27
|
|
33
28
|
setup do
|
34
|
-
|
35
|
-
|
36
|
-
|
29
|
+
# mock an authenticated service in pieces
|
30
|
+
mock_auth = Proc.new { |env|
|
31
|
+
r = CloudKit::Request.new(env)
|
32
|
+
r.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
33
|
+
}
|
34
|
+
inner_app = echo_text('martino')
|
35
|
+
service = CloudKit::Service.new(
|
36
|
+
inner_app, :collections => [:items, :things])
|
37
|
+
config = Rack::Config.new(service, &mock_auth)
|
38
|
+
authed_service = Rack::Lint.new(config)
|
37
39
|
@request = Rack::MockRequest.new(authed_service)
|
38
40
|
end
|
39
41
|
|
@@ -48,7 +50,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
48
50
|
end
|
49
51
|
|
50
52
|
should "return a 500 if authentication is configured incorrectly" do
|
51
|
-
# simulate auth requirement without
|
53
|
+
# simulate auth requirement without CLOUDKIT_AUTH_KEY being set by the
|
52
54
|
# auth filter(s)
|
53
55
|
response = @request.get('/items')
|
54
56
|
assert_equal 500, response.status
|
@@ -57,7 +59,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
57
59
|
context "on GET /cloudkit-meta" do
|
58
60
|
|
59
61
|
setup do
|
60
|
-
@response = @request.get('/cloudkit-meta',
|
62
|
+
@response = @request.get('/cloudkit-meta', VALID_TEST_AUTH)
|
61
63
|
end
|
62
64
|
|
63
65
|
should "be successful" do
|
@@ -87,11 +89,13 @@ class ServiceTest < Test::Unit::TestCase
|
|
87
89
|
setup do
|
88
90
|
3.times do |i|
|
89
91
|
json = JSON.generate(:this => i.to_s)
|
90
|
-
@
|
92
|
+
@request.put("/items/#{i}", {:input => json}.merge(VALID_TEST_AUTH))
|
91
93
|
end
|
92
94
|
json = JSON.generate(:this => '4')
|
93
|
-
@
|
94
|
-
|
95
|
+
@request.put(
|
96
|
+
'/items/4', {:input => json}.merge(CLOUDKIT_AUTH_KEY => 'someoneelse'))
|
97
|
+
@response = @request.get(
|
98
|
+
'/items', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
95
99
|
@parsed_response = JSON.parse(@response.body)
|
96
100
|
end
|
97
101
|
|
@@ -132,14 +136,14 @@ class ServiceTest < Test::Unit::TestCase
|
|
132
136
|
end
|
133
137
|
|
134
138
|
should "accept a limit parameter" do
|
135
|
-
response = @request.get('/items?limit=2',
|
139
|
+
response = @request.get('/items?limit=2', VALID_TEST_AUTH)
|
136
140
|
parsed_response = JSON.parse(response.body)
|
137
141
|
assert_equal ['/items/2', '/items/1'], parsed_response['uris']
|
138
142
|
assert_equal 3, parsed_response['total']
|
139
143
|
end
|
140
144
|
|
141
145
|
should "accept an offset parameter" do
|
142
|
-
response = @request.get('/items?offset=1',
|
146
|
+
response = @request.get('/items?offset=1', VALID_TEST_AUTH)
|
143
147
|
parsed_response = JSON.parse(response.body)
|
144
148
|
assert_equal ['/items/1', '/items/0'], parsed_response['uris']
|
145
149
|
assert_equal 1, parsed_response['offset']
|
@@ -147,7 +151,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
147
151
|
end
|
148
152
|
|
149
153
|
should "accept combined limit and offset parameters" do
|
150
|
-
response = @request.get('/items?limit=1&offset=1',
|
154
|
+
response = @request.get('/items?limit=1&offset=1', VALID_TEST_AUTH)
|
151
155
|
parsed_response = JSON.parse(response.body)
|
152
156
|
assert_equal ['/items/1'], parsed_response['uris']
|
153
157
|
assert_equal 1, parsed_response['offset']
|
@@ -155,21 +159,117 @@ class ServiceTest < Test::Unit::TestCase
|
|
155
159
|
end
|
156
160
|
|
157
161
|
should "return an empty list if no resources are found" do
|
158
|
-
response = @request.get('/things',
|
162
|
+
response = @request.get('/things', VALID_TEST_AUTH)
|
159
163
|
parsed_response = JSON.parse(response.body)
|
160
164
|
assert_equal [], parsed_response['uris']
|
161
165
|
assert_equal 0, parsed_response['total']
|
162
166
|
assert_equal 0, parsed_response['offset']
|
163
167
|
end
|
168
|
+
|
169
|
+
should "return a resolved link header" do
|
170
|
+
assert @response['Link']
|
171
|
+
assert @response['Link'].match("<http://example.org/items/_resolved>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/resolved\"")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "on GET /:collection/_resolved" do
|
176
|
+
|
177
|
+
setup do
|
178
|
+
3.times do |i|
|
179
|
+
json = JSON.generate(:this => i.to_s)
|
180
|
+
@request.put("/items/#{i}", {:input => json}.merge(VALID_TEST_AUTH))
|
181
|
+
end
|
182
|
+
json = JSON.generate(:this => '4')
|
183
|
+
@request.put(
|
184
|
+
'/items/4', {:input => json}.merge(CLOUDKIT_AUTH_KEY => 'someoneelse'))
|
185
|
+
@response = @request.get(
|
186
|
+
'/items/_resolved', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
187
|
+
@parsed_response = JSON.parse(@response.body)
|
188
|
+
end
|
189
|
+
|
190
|
+
should "be successful" do
|
191
|
+
assert_equal 200, @response.status
|
192
|
+
end
|
193
|
+
|
194
|
+
should "return all owner-originated documents" do
|
195
|
+
assert_same_elements ['/items/0', '/items/1', '/items/2'],
|
196
|
+
@parsed_response['documents'].map{|d| d['uri']}
|
197
|
+
end
|
198
|
+
|
199
|
+
should "sort descending on last_modified date" do
|
200
|
+
assert_equal ['/items/2', '/items/1', '/items/0'],
|
201
|
+
@parsed_response['documents'].map{|d| d['uri']}
|
202
|
+
end
|
203
|
+
|
204
|
+
should "return the total number of documents" do
|
205
|
+
assert @parsed_response['total']
|
206
|
+
assert_equal 3, @parsed_response['total']
|
207
|
+
end
|
208
|
+
|
209
|
+
should "return the offset" do
|
210
|
+
assert @parsed_response['offset']
|
211
|
+
assert_equal 0, @parsed_response['offset']
|
212
|
+
end
|
213
|
+
|
214
|
+
should "return a Content-Type header" do
|
215
|
+
assert_equal 'application/json', @response['Content-Type']
|
216
|
+
end
|
217
|
+
|
218
|
+
should "return an ETag" do
|
219
|
+
assert @response['ETag']
|
220
|
+
end
|
221
|
+
|
222
|
+
should "return a Last-Modified date" do
|
223
|
+
assert @response['Last-Modified']
|
224
|
+
end
|
225
|
+
|
226
|
+
should "accept a limit parameter" do
|
227
|
+
response = @request.get('/items/_resolved?limit=2', VALID_TEST_AUTH)
|
228
|
+
parsed_response = JSON.parse(response.body)
|
229
|
+
assert_equal ['/items/2', '/items/1'],
|
230
|
+
parsed_response['documents'].map{|d| d['uri']}
|
231
|
+
assert_equal 3, parsed_response['total']
|
232
|
+
end
|
233
|
+
|
234
|
+
should "accept an offset parameter" do
|
235
|
+
response = @request.get('/items/_resolved?offset=1', VALID_TEST_AUTH)
|
236
|
+
parsed_response = JSON.parse(response.body)
|
237
|
+
assert_equal ['/items/1', '/items/0'],
|
238
|
+
parsed_response['documents'].map{|d| d['uri']}
|
239
|
+
assert_equal 1, parsed_response['offset']
|
240
|
+
assert_equal 3, parsed_response['total']
|
241
|
+
end
|
242
|
+
|
243
|
+
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
|
+
assert_equal ['/items/1'],
|
247
|
+
parsed_response['documents'].map{|d| d['uri']}
|
248
|
+
assert_equal 1, parsed_response['offset']
|
249
|
+
assert_equal 3, parsed_response['total']
|
250
|
+
end
|
251
|
+
|
252
|
+
should "return an empty list if no documents are found" do
|
253
|
+
response = @request.get('/things/_resolved', VALID_TEST_AUTH)
|
254
|
+
parsed_response = JSON.parse(response.body)
|
255
|
+
assert_equal [], parsed_response['documents']
|
256
|
+
assert_equal 0, parsed_response['total']
|
257
|
+
assert_equal 0, parsed_response['offset']
|
258
|
+
end
|
259
|
+
|
260
|
+
should "return an index link header" do
|
261
|
+
assert @response['Link']
|
262
|
+
assert @response['Link'].match("<http://example.org/items>; rel=\"index\"")
|
263
|
+
end
|
164
264
|
end
|
165
265
|
|
166
266
|
context "on GET /:collection/:id" do
|
167
267
|
|
168
268
|
setup do
|
169
269
|
json = JSON.generate(:this => 'that')
|
170
|
-
@
|
270
|
+
@request.put('/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
171
271
|
@response = @request.get(
|
172
|
-
'/items/abc', 'HTTP_HOST' => 'example.org'
|
272
|
+
'/items/abc', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
173
273
|
end
|
174
274
|
|
175
275
|
should "be successful" do
|
@@ -182,7 +282,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
182
282
|
end
|
183
283
|
|
184
284
|
should "return a 404 if a document does not exist" do
|
185
|
-
response = @request.get('/items/nothing',
|
285
|
+
response = @request.get('/items/nothing', VALID_TEST_AUTH)
|
186
286
|
assert_equal 404, response.status
|
187
287
|
end
|
188
288
|
|
@@ -199,7 +299,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
199
299
|
end
|
200
300
|
|
201
301
|
should "not return documents for unauthorized users" do
|
202
|
-
response = @request.get('/items/abc',
|
302
|
+
response = @request.get('/items/abc', CLOUDKIT_AUTH_KEY => 'bogus')
|
203
303
|
assert_equal 404, response.status
|
204
304
|
end
|
205
305
|
|
@@ -215,12 +315,13 @@ class ServiceTest < Test::Unit::TestCase
|
|
215
315
|
@etags = []
|
216
316
|
4.times do |i|
|
217
317
|
json = JSON.generate(:this => i)
|
218
|
-
options = {:
|
219
|
-
options.filter_merge!(
|
220
|
-
result = @
|
221
|
-
@etags << result.
|
318
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
319
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
320
|
+
result = @request.put('/items/abc', options)
|
321
|
+
@etags << JSON.parse(result.body)['etag']
|
222
322
|
end
|
223
|
-
@response = @request.get(
|
323
|
+
@response = @request.get(
|
324
|
+
'/items/abc/versions', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
224
325
|
@parsed_response = JSON.parse(@response.body)
|
225
326
|
end
|
226
327
|
|
@@ -229,8 +330,8 @@ class ServiceTest < Test::Unit::TestCase
|
|
229
330
|
end
|
230
331
|
|
231
332
|
should "be successful even if the current resource has been deleted" do
|
232
|
-
@
|
233
|
-
response = @request.get('/items/abc/versions',
|
333
|
+
@request.delete('/items/abc', {'HTTP_IF_MATCH' => @etags.last}.merge(VALID_TEST_AUTH))
|
334
|
+
response = @request.get('/items/abc/versions', VALID_TEST_AUTH)
|
234
335
|
assert_equal 200, @response.status
|
235
336
|
parsed_response = JSON.parse(response.body)
|
236
337
|
assert_equal 4, parsed_response['uris'].size
|
@@ -243,18 +344,20 @@ class ServiceTest < Test::Unit::TestCase
|
|
243
344
|
end
|
244
345
|
|
245
346
|
should "return a 404 if the resource does not exist" do
|
246
|
-
response = @request.get('/items/nothing/versions',
|
347
|
+
response = @request.get('/items/nothing/versions', VALID_TEST_AUTH)
|
247
348
|
assert_equal 404, response.status
|
248
349
|
end
|
249
350
|
|
250
351
|
should "return a 404 for non-owner-originated requests" do
|
251
|
-
response = @request.get(
|
352
|
+
response = @request.get(
|
353
|
+
'/items/abc/versions', CLOUDKIT_AUTH_KEY => 'someoneelse')
|
252
354
|
assert_equal 404, response.status
|
253
355
|
end
|
254
356
|
|
255
357
|
should "sort descending on last_modified date" do
|
256
|
-
assert_equal
|
257
|
-
|
358
|
+
assert_equal(
|
359
|
+
['/items/abc'].concat(@etags[0..-2].reverse.map{|e| "/items/abc/versions/#{e}"}),
|
360
|
+
@parsed_response['uris'])
|
258
361
|
end
|
259
362
|
|
260
363
|
should "return the total number of uris" do
|
@@ -280,7 +383,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
280
383
|
end
|
281
384
|
|
282
385
|
should "accept a limit parameter" do
|
283
|
-
response = @request.get('/items/abc/versions?limit=2',
|
386
|
+
response = @request.get('/items/abc/versions?limit=2', VALID_TEST_AUTH)
|
284
387
|
parsed_response = JSON.parse(response.body)
|
285
388
|
assert_equal ['/items/abc', "/items/abc/versions/#{@etags[-2]}"],
|
286
389
|
parsed_response['uris']
|
@@ -288,7 +391,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
288
391
|
end
|
289
392
|
|
290
393
|
should "accept an offset parameter" do
|
291
|
-
response = @request.get('/items/abc/versions?offset=1',
|
394
|
+
response = @request.get('/items/abc/versions?offset=1', VALID_TEST_AUTH)
|
292
395
|
parsed_response = JSON.parse(response.body)
|
293
396
|
assert_equal @etags.reverse[1..-1].map{|e| "/items/abc/versions/#{e}"},
|
294
397
|
parsed_response['uris']
|
@@ -297,12 +400,125 @@ class ServiceTest < Test::Unit::TestCase
|
|
297
400
|
end
|
298
401
|
|
299
402
|
should "accept combined limit and offset parameters" do
|
300
|
-
response = @request.get('/items/abc/versions?limit=1&offset=1',
|
403
|
+
response = @request.get('/items/abc/versions?limit=1&offset=1', VALID_TEST_AUTH)
|
301
404
|
parsed_response = JSON.parse(response.body)
|
302
405
|
assert_equal ["/items/abc/versions/#{@etags[-2]}"], parsed_response['uris']
|
303
406
|
assert_equal 1, parsed_response['offset']
|
304
407
|
assert_equal 4, parsed_response['total']
|
305
408
|
end
|
409
|
+
|
410
|
+
should "return a resolved link header" do
|
411
|
+
assert @response['Link']
|
412
|
+
assert @response['Link'].match("<http://example.org/items/abc/versions/_resolved>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/resolved\"")
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
context "on GET /:collections/:id/versions/_resolved" do
|
417
|
+
|
418
|
+
setup do
|
419
|
+
@etags = []
|
420
|
+
4.times do |i|
|
421
|
+
json = JSON.generate(:this => i)
|
422
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
423
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
424
|
+
result = @request.put('/items/abc', options)
|
425
|
+
@etags << JSON.parse(result.body)['etag']
|
426
|
+
end
|
427
|
+
@response = @request.get(
|
428
|
+
'/items/abc/versions/_resolved', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
429
|
+
@parsed_response = JSON.parse(@response.body)
|
430
|
+
end
|
431
|
+
|
432
|
+
should "be successful" do
|
433
|
+
assert_equal 200, @response.status
|
434
|
+
end
|
435
|
+
|
436
|
+
should "be successful even if the current resource has been deleted" do
|
437
|
+
@request.delete(
|
438
|
+
'/items/abc', {'HTTP_IF_MATCH' => @etags.last}.merge(VALID_TEST_AUTH))
|
439
|
+
response = @request.get('/items/abc/versions/_resolved', VALID_TEST_AUTH)
|
440
|
+
assert_equal 200, @response.status
|
441
|
+
parsed_response = JSON.parse(response.body)
|
442
|
+
assert_equal 4, parsed_response['documents'].size
|
443
|
+
end
|
444
|
+
|
445
|
+
should "return all versions of a document" do
|
446
|
+
documents = @parsed_response['documents']
|
447
|
+
assert documents
|
448
|
+
assert_equal 4, documents.size
|
449
|
+
end
|
450
|
+
|
451
|
+
should "return a 404 if the resource does not exist" do
|
452
|
+
response = @request.get('/items/nothing/versions/_resolved', VALID_TEST_AUTH)
|
453
|
+
assert_equal 404, response.status
|
454
|
+
end
|
455
|
+
|
456
|
+
should "return a 404 for non-owner-originated requests" do
|
457
|
+
response = @request.get('/items/abc/versions/_resolved', CLOUDKIT_AUTH_KEY => 'someoneelse')
|
458
|
+
assert_equal 404, response.status
|
459
|
+
end
|
460
|
+
|
461
|
+
should "sort descending on last_modified date" do
|
462
|
+
assert_equal(
|
463
|
+
['/items/abc'].concat(@etags[0..-2].reverse.map{|e| "/items/abc/versions/#{e}"}),
|
464
|
+
@parsed_response['documents'].map{|d| d['uri']})
|
465
|
+
end
|
466
|
+
|
467
|
+
should "return the total number of documents" do
|
468
|
+
assert @parsed_response['total']
|
469
|
+
assert_equal 4, @parsed_response['total']
|
470
|
+
end
|
471
|
+
|
472
|
+
should "return the offset" do
|
473
|
+
assert @parsed_response['offset']
|
474
|
+
assert_equal 0, @parsed_response['offset']
|
475
|
+
end
|
476
|
+
|
477
|
+
should "return a Content-Type header" do
|
478
|
+
assert_equal 'application/json', @response['Content-Type']
|
479
|
+
end
|
480
|
+
|
481
|
+
should "return an ETag" do
|
482
|
+
assert @response['ETag']
|
483
|
+
end
|
484
|
+
|
485
|
+
should "return a Last-Modified date" do
|
486
|
+
assert @response['Last-Modified']
|
487
|
+
end
|
488
|
+
|
489
|
+
should "accept a limit parameter" do
|
490
|
+
response = @request.get(
|
491
|
+
'/items/abc/versions/_resolved?limit=2', VALID_TEST_AUTH)
|
492
|
+
parsed_response = JSON.parse(response.body)
|
493
|
+
assert_equal ['/items/abc', "/items/abc/versions/#{@etags[-2]}"],
|
494
|
+
parsed_response['documents'].map{|d| d['uri']}
|
495
|
+
assert_equal 4, parsed_response['total']
|
496
|
+
end
|
497
|
+
|
498
|
+
should "accept an offset parameter" do
|
499
|
+
response = @request.get(
|
500
|
+
'/items/abc/versions/_resolved?offset=1', VALID_TEST_AUTH)
|
501
|
+
parsed_response = JSON.parse(response.body)
|
502
|
+
assert_equal @etags.reverse[1..-1].map{|e| "/items/abc/versions/#{e}"},
|
503
|
+
parsed_response['documents'].map{|d| d['uri']}
|
504
|
+
assert_equal 1, parsed_response['offset']
|
505
|
+
assert_equal 4, parsed_response['total']
|
506
|
+
end
|
507
|
+
|
508
|
+
should "accept combined limit and offset parameters" do
|
509
|
+
response = @request.get(
|
510
|
+
'/items/abc/versions/_resolved?limit=1&offset=1', VALID_TEST_AUTH)
|
511
|
+
parsed_response = JSON.parse(response.body)
|
512
|
+
assert_equal ["/items/abc/versions/#{@etags[-2]}"],
|
513
|
+
parsed_response['documents'].map{|d| d['uri']}
|
514
|
+
assert_equal 1, parsed_response['offset']
|
515
|
+
assert_equal 4, parsed_response['total']
|
516
|
+
end
|
517
|
+
|
518
|
+
should "return an index link header" do
|
519
|
+
assert @response['Link']
|
520
|
+
assert @response['Link'].match("<http://example.org/items/abc/versions>; rel=\"index\"")
|
521
|
+
end
|
306
522
|
end
|
307
523
|
|
308
524
|
context "on GET /:collection/:id/versions/:etag" do
|
@@ -311,13 +527,13 @@ class ServiceTest < Test::Unit::TestCase
|
|
311
527
|
@etags = []
|
312
528
|
2.times do |i|
|
313
529
|
json = JSON.generate(:this => i)
|
314
|
-
options = {:
|
315
|
-
options.filter_merge!(
|
316
|
-
result = @
|
317
|
-
@etags << result.
|
530
|
+
options = {:input => json}.merge(VALID_TEST_AUTH)
|
531
|
+
options.filter_merge!('HTTP_IF_MATCH' => @etags.try(:last))
|
532
|
+
result = @request.put('/items/abc', options)
|
533
|
+
@etags << JSON.parse(result.body)['etag']
|
318
534
|
end
|
319
535
|
@response = @request.get(
|
320
|
-
"/items/abc/versions/#{@etags.first}",
|
536
|
+
"/items/abc/versions/#{@etags.first}", VALID_TEST_AUTH)
|
321
537
|
@parsed_response = JSON.parse(@response.body)
|
322
538
|
end
|
323
539
|
|
@@ -331,7 +547,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
331
547
|
|
332
548
|
should "return a 404 if a document is not found" do
|
333
549
|
response = @request.get(
|
334
|
-
"/items/nothing/versions/#{@etags.first}",
|
550
|
+
"/items/nothing/versions/#{@etags.first}", VALID_TEST_AUTH)
|
335
551
|
assert_equal 404, response.status
|
336
552
|
end
|
337
553
|
|
@@ -349,7 +565,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
349
565
|
|
350
566
|
should "not return documents for unauthorized users" do
|
351
567
|
response = @request.get(
|
352
|
-
"/items/abc/versions/#{@etags.first}",
|
568
|
+
"/items/abc/versions/#{@etags.first}", CLOUDKIT_AUTH_KEY => 'someoneelse')
|
353
569
|
assert_equal 404, response.status
|
354
570
|
end
|
355
571
|
end
|
@@ -359,12 +575,12 @@ class ServiceTest < Test::Unit::TestCase
|
|
359
575
|
setup do
|
360
576
|
json = JSON.generate(:this => 'that')
|
361
577
|
@response = @request.post(
|
362
|
-
'/items', :input => json
|
578
|
+
'/items', {:input => json}.merge(VALID_TEST_AUTH))
|
363
579
|
@body = JSON.parse(@response.body)
|
364
580
|
end
|
365
581
|
|
366
582
|
should "store the document" do
|
367
|
-
result = @
|
583
|
+
result = @request.get(@body['uri'], VALID_TEST_AUTH)
|
368
584
|
assert_equal 200, result.status
|
369
585
|
end
|
370
586
|
|
@@ -390,7 +606,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
390
606
|
end
|
391
607
|
|
392
608
|
should "return a 422 if parsing fails" do
|
393
|
-
response = @request.post('/items', :input => 'fail'
|
609
|
+
response = @request.post('/items', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
394
610
|
assert_equal 422, response.status
|
395
611
|
end
|
396
612
|
|
@@ -417,31 +633,32 @@ class ServiceTest < Test::Unit::TestCase
|
|
417
633
|
|
418
634
|
setup do
|
419
635
|
json = JSON.generate(:this => 'that')
|
420
|
-
@original = @
|
421
|
-
|
422
|
-
|
636
|
+
@original = @request.put(
|
637
|
+
'/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
638
|
+
etag = JSON.parse(@original.body)['etag']
|
639
|
+
json = JSON.generate(:this => 'other')
|
423
640
|
@response = @request.put(
|
424
641
|
'/items/abc',
|
425
|
-
:input
|
426
|
-
'HTTP_IF_MATCH'
|
427
|
-
|
642
|
+
:input => json,
|
643
|
+
'HTTP_IF_MATCH' => etag,
|
644
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
428
645
|
@json = JSON.parse(@response.body)
|
429
646
|
end
|
430
647
|
|
431
648
|
should "create a document if it does not already exist" do
|
432
649
|
json = JSON.generate(:this => 'thing')
|
433
650
|
response = @request.put(
|
434
|
-
'/items/xyz', :input => json
|
651
|
+
'/items/xyz', {:input => json}.merge(VALID_TEST_AUTH))
|
435
652
|
assert_equal 201, response.status
|
436
|
-
result = @
|
653
|
+
result = @request.get('/items/xyz', VALID_TEST_AUTH)
|
437
654
|
assert_equal 200, result.status
|
438
|
-
assert_equal 'thing', result.
|
655
|
+
assert_equal 'thing', JSON.parse(result.body)['this']
|
439
656
|
end
|
440
657
|
|
441
658
|
should "update the document if it already exists" do
|
442
659
|
assert_equal 200, @response.status
|
443
|
-
result = @
|
444
|
-
assert_equal 'other', result['this']
|
660
|
+
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
661
|
+
assert_equal 'other', JSON.parse(result.body)['this']
|
445
662
|
end
|
446
663
|
|
447
664
|
should "return the metadata" do
|
@@ -465,9 +682,9 @@ class ServiceTest < Test::Unit::TestCase
|
|
465
682
|
json = JSON.generate(:this => 'other')
|
466
683
|
response = @request.put(
|
467
684
|
'/items/abc',
|
468
|
-
:input
|
469
|
-
'HTTP_IF_MATCH'
|
470
|
-
|
685
|
+
:input => json,
|
686
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
687
|
+
CLOUDKIT_AUTH_KEY => 'someone_else')
|
471
688
|
assert_equal 404, response.status
|
472
689
|
end
|
473
690
|
|
@@ -476,15 +693,15 @@ class ServiceTest < Test::Unit::TestCase
|
|
476
693
|
client_b_input = JSON.generate(:other => 'thing')
|
477
694
|
response = @request.put(
|
478
695
|
'/items/abc',
|
479
|
-
:input
|
480
|
-
'HTTP_IF_MATCH'
|
481
|
-
|
696
|
+
:input => client_a_input,
|
697
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
698
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
482
699
|
assert_equal 200, response.status
|
483
700
|
response = @request.put(
|
484
701
|
'/items/abc',
|
485
|
-
:input
|
486
|
-
'HTTP_IF_MATCH'
|
487
|
-
|
702
|
+
:input => client_b_input,
|
703
|
+
'HTTP_IF_MATCH' => @json['etag'],
|
704
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
488
705
|
assert_equal 412, response.status
|
489
706
|
end
|
490
707
|
|
@@ -492,14 +709,13 @@ class ServiceTest < Test::Unit::TestCase
|
|
492
709
|
json = JSON.generate(:this => 'updated')
|
493
710
|
response = @request.put(
|
494
711
|
'/items/abc',
|
495
|
-
:input => json
|
496
|
-
auth_key => remote_user)
|
712
|
+
{:input => json}.merge(VALID_TEST_AUTH))
|
497
713
|
assert_equal 400, response.status
|
498
714
|
end
|
499
715
|
|
500
716
|
should "return a 422 if parsing fails" do
|
501
717
|
response = @request.put(
|
502
|
-
'/items/zzz', :input => 'fail'
|
718
|
+
'/items/zzz', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
503
719
|
assert_equal 422, response.status
|
504
720
|
end
|
505
721
|
|
@@ -509,15 +725,15 @@ class ServiceTest < Test::Unit::TestCase
|
|
509
725
|
'/items/abc',
|
510
726
|
:input => json,
|
511
727
|
'HTTP_IF_MATCH' => @json['etag'],
|
512
|
-
|
728
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
513
729
|
assert_equal 200, response.status
|
514
730
|
etag = JSON.parse(response.body)['etag']
|
515
731
|
json = JSON.generate(:this => 'updated again')
|
516
732
|
new_response = @request.put(
|
517
733
|
'/items/abc',
|
518
|
-
:input
|
519
|
-
'HTTP_IF_MATCH'
|
520
|
-
|
734
|
+
:input => json,
|
735
|
+
'HTTP_IF_MATCH' => etag,
|
736
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
521
737
|
assert_equal 200, new_response.status
|
522
738
|
new_etag = JSON.parse(new_response.body)['etag']
|
523
739
|
assert_not_equal etag, new_etag
|
@@ -550,25 +766,26 @@ class ServiceTest < Test::Unit::TestCase
|
|
550
766
|
|
551
767
|
setup do
|
552
768
|
json = JSON.generate(:this => 'that')
|
553
|
-
@result = @
|
554
|
-
|
769
|
+
@result = @request.put(
|
770
|
+
'/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
771
|
+
@etag = JSON.parse(@result.body)['etag']
|
555
772
|
end
|
556
773
|
|
557
774
|
should "delete the document" do
|
558
775
|
response = @request.delete(
|
559
776
|
'/items/abc',
|
560
|
-
'HTTP_IF_MATCH'
|
561
|
-
|
777
|
+
'HTTP_IF_MATCH' => @etag,
|
778
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
562
779
|
assert_equal 200, response.status
|
563
|
-
result = @
|
780
|
+
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
564
781
|
assert_equal 410, result.status
|
565
782
|
end
|
566
783
|
|
567
784
|
should "return the metadata" do
|
568
785
|
response = @request.delete(
|
569
786
|
'/items/abc',
|
570
|
-
'HTTP_IF_MATCH'
|
571
|
-
|
787
|
+
'HTTP_IF_MATCH' => @etag,
|
788
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
572
789
|
json = JSON.parse(response.body)
|
573
790
|
assert_equal 4, json.keys.size
|
574
791
|
assert_same_elements ['ok', 'uri', 'etag', 'last_modified'], json.keys
|
@@ -577,79 +794,80 @@ class ServiceTest < Test::Unit::TestCase
|
|
577
794
|
should "set the Content-Type header" do
|
578
795
|
response = @request.delete(
|
579
796
|
'/items/abc',
|
580
|
-
'HTTP_IF_MATCH'
|
581
|
-
|
797
|
+
'HTTP_IF_MATCH' => @etag,
|
798
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
582
799
|
assert_equal 'application/json', response['Content-Type']
|
583
800
|
end
|
584
801
|
|
585
802
|
should "not set an ETag header" do
|
586
803
|
response = @request.delete(
|
587
804
|
'/items/abc',
|
588
|
-
'HTTP_IF_MATCH'
|
589
|
-
|
805
|
+
'HTTP_IF_MATCH' => @etag,
|
806
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
590
807
|
assert_nil response['ETag']
|
591
808
|
end
|
592
809
|
|
593
810
|
should "not set a Last-Modified header" do
|
594
811
|
response = @request.delete(
|
595
812
|
'/items/abc',
|
596
|
-
'HTTP_IF_MATCH'
|
597
|
-
|
813
|
+
'HTTP_IF_MATCH' => @etag,
|
814
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
598
815
|
assert_nil response['Last-Modified']
|
599
816
|
end
|
600
817
|
|
601
818
|
should "return a 404 for items that have never existed" do
|
602
819
|
response = @request.delete(
|
603
820
|
'/items/zzz',
|
604
|
-
'HTTP_IF_MATCH'
|
605
|
-
|
821
|
+
'HTTP_IF_MATCH' => @etag,
|
822
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
606
823
|
assert_equal 404, response.status
|
607
824
|
end
|
608
825
|
|
609
826
|
should "require an ETag" do
|
610
827
|
response = @request.delete(
|
611
828
|
'/items/abc',
|
612
|
-
|
829
|
+
VALID_TEST_AUTH)
|
613
830
|
assert_equal 400, response.status
|
614
831
|
end
|
615
832
|
|
616
833
|
should "verify the user in the doc" do
|
617
834
|
response = @request.delete(
|
618
835
|
'/items/abc',
|
619
|
-
'HTTP_IF_MATCH'
|
620
|
-
|
836
|
+
'HTTP_IF_MATCH' => @etag,
|
837
|
+
CLOUDKIT_AUTH_KEY => 'someoneelse')
|
621
838
|
assert_equal 404, response.status
|
622
839
|
end
|
623
840
|
|
624
841
|
should "detect and return conflicts" do
|
625
842
|
json = JSON.generate(:this => 'that')
|
626
|
-
result = @
|
627
|
-
|
843
|
+
result = @request.put(
|
844
|
+
'/items/123', {:input => json}.merge(VALID_TEST_AUTH))
|
845
|
+
etag = JSON.parse(result.body)['etag']
|
628
846
|
client_a_input = JSON.generate(:this => 'updated')
|
629
847
|
client_b_input = JSON.generate(:other => 'thing')
|
630
848
|
response = @request.put(
|
631
849
|
'/items/123',
|
632
|
-
:input
|
633
|
-
'HTTP_IF_MATCH'
|
634
|
-
|
850
|
+
:input => client_a_input,
|
851
|
+
'HTTP_IF_MATCH' => etag,
|
852
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
635
853
|
assert_equal 200, response.status
|
636
854
|
response = @request.delete(
|
637
855
|
'/items/123',
|
638
|
-
:input
|
639
|
-
'HTTP_IF_MATCH'
|
640
|
-
|
856
|
+
:input => client_b_input,
|
857
|
+
'HTTP_IF_MATCH' => etag,
|
858
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
641
859
|
assert_equal 412, response.status
|
642
860
|
end
|
643
861
|
|
644
862
|
should "retain version history" do
|
645
863
|
response = @request.delete(
|
646
864
|
'/items/abc',
|
647
|
-
'HTTP_IF_MATCH'
|
648
|
-
|
865
|
+
'HTTP_IF_MATCH' => @etag,
|
866
|
+
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
649
867
|
assert_equal 200, response.status
|
650
868
|
response = @request.get(
|
651
869
|
'/items/abc/versions',
|
652
|
-
|
870
|
+
VALID_TEST_AUTH)
|
653
871
|
json = JSON.parse(response.body)
|
654
872
|
assert_equal 1, json['total']
|
655
873
|
end
|
@@ -674,7 +892,7 @@ class ServiceTest < Test::Unit::TestCase
|
|
674
892
|
context "on OPTIONS /:collection" do
|
675
893
|
|
676
894
|
setup do
|
677
|
-
@response = @request.request('OPTIONS', '/items',
|
895
|
+
@response = @request.request('OPTIONS', '/items', VALID_TEST_AUTH)
|
678
896
|
end
|
679
897
|
|
680
898
|
should "return a 200 status" do
|
@@ -688,10 +906,13 @@ class ServiceTest < Test::Unit::TestCase
|
|
688
906
|
end
|
689
907
|
end
|
690
908
|
|
909
|
+
context "on OPTIONS /:collection/_resolved" do
|
910
|
+
end
|
911
|
+
|
691
912
|
context "on OPTIONS /:collection/:id" do
|
692
913
|
|
693
914
|
setup do
|
694
|
-
@response = @request.request('OPTIONS', '/items/xyz',
|
915
|
+
@response = @request.request('OPTIONS', '/items/xyz', VALID_TEST_AUTH)
|
695
916
|
end
|
696
917
|
|
697
918
|
should "return a 200 status" do
|
@@ -708,6 +929,9 @@ class ServiceTest < Test::Unit::TestCase
|
|
708
929
|
context "on OPTIONS /:collection/:id/versions" do
|
709
930
|
end
|
710
931
|
|
932
|
+
context "on OPTIONS /:collection/:id/versions/_resolved" do
|
933
|
+
end
|
934
|
+
|
711
935
|
context "on OPTIONS /:collection/:id/versions/:etag" do
|
712
936
|
end
|
713
937
|
|
@@ -715,10 +939,10 @@ class ServiceTest < Test::Unit::TestCase
|
|
715
939
|
|
716
940
|
should "return an empty body" do
|
717
941
|
json = JSON.generate(:this => 'that')
|
718
|
-
@
|
719
|
-
response = @request.request('HEAD', '/items/abc',
|
942
|
+
@request.put('/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
943
|
+
response = @request.request('HEAD', '/items/abc', VALID_TEST_AUTH)
|
720
944
|
assert_equal '', response.body
|
721
|
-
response = @request.request('HEAD', '/items',
|
945
|
+
response = @request.request('HEAD', '/items', VALID_TEST_AUTH)
|
722
946
|
assert_equal '', response.body
|
723
947
|
end
|
724
948
|
|