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
data/test/request_test.rb
DELETED
@@ -1,197 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
class RequestTest < Test::Unit::TestCase
|
3
|
-
|
4
|
-
context "A Request" do
|
5
|
-
|
6
|
-
should "match requests with routes" do
|
7
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
8
|
-
'http://example.com')).match?(
|
9
|
-
'GET', '/')
|
10
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
11
|
-
'http://example.com/')).match?(
|
12
|
-
'GET', '/')
|
13
|
-
assert !CloudKit::Request.new(Rack::MockRequest.env_for(
|
14
|
-
'http://example.com/')).match?(
|
15
|
-
'POST', '/')
|
16
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
17
|
-
'http://example.com/hello')).match?(
|
18
|
-
'GET', '/hello')
|
19
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
20
|
-
'http://example.com/hello')).match?(
|
21
|
-
'GET', '/hello')
|
22
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
23
|
-
'http://example.com/hello', :method => 'POST')).match?(
|
24
|
-
'POST', '/hello')
|
25
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
26
|
-
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
27
|
-
'POST', '/hello', [{'q' => 'a'}])
|
28
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
29
|
-
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
30
|
-
'POST', '/hello', ['q'])
|
31
|
-
assert !CloudKit::Request.new(Rack::MockRequest.env_for(
|
32
|
-
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
33
|
-
'POST', '/hello', [{'q' => 'b'}])
|
34
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
35
|
-
'http://example.com/hello?q', :method => 'POST')).match?(
|
36
|
-
'POST', '/hello', [{'q' => nil}])
|
37
|
-
assert !CloudKit::Request.new(Rack::MockRequest.env_for(
|
38
|
-
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
39
|
-
'POST', '/hello', [{'q' => nil}])
|
40
|
-
assert !CloudKit::Request.new(Rack::MockRequest.env_for(
|
41
|
-
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
42
|
-
'POST', '/hello', [{'q' => ''}])
|
43
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
44
|
-
'http://example.com/hello?q&x=y', :method => 'PUT')).match?(
|
45
|
-
'PUT', '/hello', ['q', {'x' => 'y'}])
|
46
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
47
|
-
'http://example.com/hello?q&x=y&z', :method => 'PUT')).match?(
|
48
|
-
'PUT', '/hello', ['q', {'x' => 'y'}])
|
49
|
-
assert !CloudKit::Request.new(Rack::MockRequest.env_for(
|
50
|
-
'http://example.com/hello?q&x=y', :method => 'PUT')).match?(
|
51
|
-
'PUT', '/hello', [{'q' => 'a'},{'x' => 'y'}])
|
52
|
-
end
|
53
|
-
|
54
|
-
should "treat a trailing :id as a wildcard for path matching" do
|
55
|
-
assert CloudKit::Request.new(Rack::MockRequest.env_for(
|
56
|
-
'http://example.com/hello/123')).match?('GET', '/hello/:id')
|
57
|
-
end
|
58
|
-
|
59
|
-
should "inject stack-internal via-style env vars" do
|
60
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/test'))
|
61
|
-
assert_equal [], request.via
|
62
|
-
request.inject_via('a.b')
|
63
|
-
assert request.via.include?('a.b')
|
64
|
-
request.inject_via('c.d')
|
65
|
-
assert request.via.include?('a.b')
|
66
|
-
assert request.via.include?('c.d')
|
67
|
-
end
|
68
|
-
|
69
|
-
should "announce the use of auth middleware" do
|
70
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
71
|
-
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
72
|
-
assert request.via.include?(CLOUDKIT_OAUTH_FILTER_KEY)
|
73
|
-
end
|
74
|
-
|
75
|
-
should "know if auth provided by upstream middleware" do
|
76
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
77
|
-
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
78
|
-
assert request.using_auth?
|
79
|
-
end
|
80
|
-
|
81
|
-
should "know the current user" do
|
82
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
83
|
-
assert_nil request.current_user
|
84
|
-
request = CloudKit::Request.new(
|
85
|
-
Rack::MockRequest.env_for('/', CLOUDKIT_AUTH_KEY => 'cecil'))
|
86
|
-
assert request.current_user
|
87
|
-
assert_equal 'cecil', request.current_user
|
88
|
-
end
|
89
|
-
|
90
|
-
should "set the current user" do
|
91
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
92
|
-
request.current_user = 'cecil'
|
93
|
-
assert request.current_user
|
94
|
-
assert_equal 'cecil', request.current_user
|
95
|
-
end
|
96
|
-
|
97
|
-
should "know the login url" do
|
98
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
99
|
-
assert_equal '/login', request.login_url
|
100
|
-
request = CloudKit::Request.new(
|
101
|
-
Rack::MockRequest.env_for(
|
102
|
-
'/', CLOUDKIT_LOGIN_URL => '/sessions'))
|
103
|
-
assert_equal '/sessions', request.login_url
|
104
|
-
end
|
105
|
-
|
106
|
-
should "set the login url" do
|
107
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
108
|
-
request.login_url = '/welcome'
|
109
|
-
assert_equal '/welcome', request.login_url
|
110
|
-
end
|
111
|
-
|
112
|
-
should "know the logout url" do
|
113
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
114
|
-
assert_equal '/logout', request.logout_url
|
115
|
-
request = CloudKit::Request.new(
|
116
|
-
Rack::MockRequest.env_for(
|
117
|
-
'/', CLOUDKIT_LOGOUT_URL => '/sessions'))
|
118
|
-
assert_equal '/sessions', request.logout_url
|
119
|
-
end
|
120
|
-
|
121
|
-
should "set the logout url" do
|
122
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
123
|
-
request.logout_url = '/goodbye'
|
124
|
-
assert_equal '/goodbye', request.logout_url
|
125
|
-
end
|
126
|
-
|
127
|
-
should "get the session" do
|
128
|
-
request = CloudKit::Request.new(
|
129
|
-
Rack::MockRequest.env_for('/', 'rack.session' => 'this'))
|
130
|
-
assert request.session
|
131
|
-
assert_equal 'this', request.session
|
132
|
-
end
|
133
|
-
|
134
|
-
should "know the flash" do
|
135
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
136
|
-
'/', 'rack.session' => {}))
|
137
|
-
assert request.flash.is_a?(CloudKit::FlashSession)
|
138
|
-
end
|
139
|
-
|
140
|
-
should "parse if-match headers" do
|
141
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
142
|
-
'/items/123/versions'))
|
143
|
-
assert_nil request.if_match
|
144
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
145
|
-
'/items/123/versions',
|
146
|
-
'HTTP_IF_MATCH' => '"a"'))
|
147
|
-
assert_equal 'a', request.if_match
|
148
|
-
end
|
149
|
-
|
150
|
-
should "treat a list of etags in an if-match header as a single etag" do
|
151
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
152
|
-
'/items/123/versions',
|
153
|
-
'HTTP_IF_MATCH' => '"a", "b"'))
|
154
|
-
# See CloudKit::Request#if_match for more info on this expectation
|
155
|
-
assert_equal 'a", "b', request.if_match
|
156
|
-
end
|
157
|
-
|
158
|
-
should "ignore if-match when set to *" do
|
159
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
160
|
-
'/items/123/versions',
|
161
|
-
'HTTP_IF_MATCH' => '*'))
|
162
|
-
assert_nil request.if_match
|
163
|
-
end
|
164
|
-
|
165
|
-
should "understand header auth" do
|
166
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
167
|
-
'http://photos.example.net/photos?file=vacation.jpg&size=original',
|
168
|
-
'Authorization' =>
|
169
|
-
'OAuth realm="",' +
|
170
|
-
'oauth_version="1.0",' +
|
171
|
-
'oauth_consumer_key="dpf43f3p2l4k3l03",' +
|
172
|
-
'oauth_token="nnch734d00sl2jdk",' +
|
173
|
-
'oauth_timestamp="1191242096",' +
|
174
|
-
'oauth_nonce="kllo9940pd9333jh",' +
|
175
|
-
'oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",' +
|
176
|
-
'oauth_signature_method="HMAC-SHA1"'))
|
177
|
-
assert_equal 'dpf43f3p2l4k3l03', request['oauth_consumer_key']
|
178
|
-
assert_equal 'nnch734d00sl2jdk', request['oauth_token']
|
179
|
-
assert_equal '1191242096', request['oauth_timestamp']
|
180
|
-
assert_equal 'kllo9940pd9333jh', request['oauth_nonce']
|
181
|
-
assert_equal 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=', request['oauth_signature']
|
182
|
-
assert_equal 'HMAC-SHA1', request['oauth_signature_method']
|
183
|
-
end
|
184
|
-
|
185
|
-
should "know the last path element" do
|
186
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
187
|
-
assert_nil request.last_path_element
|
188
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc'))
|
189
|
-
assert_equal 'abc', request.last_path_element
|
190
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc/'))
|
191
|
-
assert_equal 'abc', request.last_path_element
|
192
|
-
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc/def'))
|
193
|
-
assert_equal 'def', request.last_path_element
|
194
|
-
end
|
195
|
-
|
196
|
-
end
|
197
|
-
end
|
data/test/service_test.rb
DELETED
@@ -1,971 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
class ServiceTest < Test::Unit::TestCase
|
3
|
-
|
4
|
-
context "A CloudKit::Service" do
|
5
|
-
|
6
|
-
should "return a 501 for unimplemented methods" do
|
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')
|
14
|
-
assert_equal 501, response.status
|
15
|
-
|
16
|
-
# disable Rack::Lint so that an invalid HTTP method
|
17
|
-
# can be tested
|
18
|
-
app = Rack::Builder.new {
|
19
|
-
use CloudKit::Service, :collections => [:items, :things]
|
20
|
-
run echo_text('nothing')
|
21
|
-
}
|
22
|
-
response = Rack::MockRequest.new(app).request('REJUXTAPOSE', '/items')
|
23
|
-
assert_equal 501, response.status
|
24
|
-
end
|
25
|
-
|
26
|
-
context "using auth" do
|
27
|
-
|
28
|
-
setup do
|
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)
|
39
|
-
@request = Rack::MockRequest.new(authed_service)
|
40
|
-
end
|
41
|
-
|
42
|
-
should "allow requests for / to pass through" do
|
43
|
-
response = @request.get('/')
|
44
|
-
assert_equal 'martino', response.body
|
45
|
-
end
|
46
|
-
|
47
|
-
should "allow any non-specified resource request to pass through" do
|
48
|
-
response = @request.get('/hammers')
|
49
|
-
assert_equal 'martino', response.body
|
50
|
-
end
|
51
|
-
|
52
|
-
should "return a 500 if authentication is configured incorrectly" do
|
53
|
-
# simulate auth requirement without CLOUDKIT_AUTH_KEY being set by the
|
54
|
-
# auth filter(s)
|
55
|
-
response = @request.get('/items')
|
56
|
-
assert_equal 500, response.status
|
57
|
-
end
|
58
|
-
|
59
|
-
context "on GET /cloudkit-meta" do
|
60
|
-
|
61
|
-
setup do
|
62
|
-
@response = @request.get('/cloudkit-meta', VALID_TEST_AUTH)
|
63
|
-
end
|
64
|
-
|
65
|
-
should "be successful" do
|
66
|
-
assert_equal 200, @response.status
|
67
|
-
end
|
68
|
-
|
69
|
-
should "return a list of hosted collection URIs" do
|
70
|
-
uris = JSON.parse(@response.body)['uris']
|
71
|
-
assert_same_elements ['/things', '/items'], uris
|
72
|
-
end
|
73
|
-
|
74
|
-
should "return a Content-Type header" do
|
75
|
-
assert_equal 'application/json', @response['Content-Type']
|
76
|
-
end
|
77
|
-
|
78
|
-
should "return an ETag" do
|
79
|
-
assert @response['ETag']
|
80
|
-
end
|
81
|
-
|
82
|
-
should "not set a Last-Modified header" do
|
83
|
-
assert_nil @response['Last-Modified']
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
context "on GET /:collection" do
|
88
|
-
|
89
|
-
setup do
|
90
|
-
3.times do |i|
|
91
|
-
json = JSON.generate(:this => i.to_s)
|
92
|
-
@request.put("/items/#{i}", {:input => json}.merge(VALID_TEST_AUTH))
|
93
|
-
end
|
94
|
-
json = JSON.generate(:this => '4')
|
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))
|
99
|
-
@parsed_response = JSON.parse(@response.body)
|
100
|
-
end
|
101
|
-
|
102
|
-
should "be successful" do
|
103
|
-
assert_equal 200, @response.status
|
104
|
-
end
|
105
|
-
|
106
|
-
should "return a list of URIs for all owner-originated resources" do
|
107
|
-
assert_same_elements ['/items/0', '/items/1', '/items/2'],
|
108
|
-
@parsed_response['uris']
|
109
|
-
end
|
110
|
-
|
111
|
-
should "sort descending on last_modified date" do
|
112
|
-
assert_equal ['/items/2', '/items/1', '/items/0'],
|
113
|
-
@parsed_response['uris']
|
114
|
-
end
|
115
|
-
|
116
|
-
should "return the total number of uris" do
|
117
|
-
assert @parsed_response['total']
|
118
|
-
assert_equal 3, @parsed_response['total']
|
119
|
-
end
|
120
|
-
|
121
|
-
should "return the offset" do
|
122
|
-
assert @parsed_response['offset']
|
123
|
-
assert_equal 0, @parsed_response['offset']
|
124
|
-
end
|
125
|
-
|
126
|
-
should "return a Content-Type header" do
|
127
|
-
assert_equal 'application/json', @response['Content-Type']
|
128
|
-
end
|
129
|
-
|
130
|
-
should "return an ETag" do
|
131
|
-
assert @response['ETag']
|
132
|
-
end
|
133
|
-
|
134
|
-
should "return a Last-Modified date" do
|
135
|
-
assert @response['Last-Modified']
|
136
|
-
end
|
137
|
-
|
138
|
-
should "accept a limit parameter" do
|
139
|
-
response = @request.get('/items?limit=2', VALID_TEST_AUTH)
|
140
|
-
parsed_response = JSON.parse(response.body)
|
141
|
-
assert_equal ['/items/2', '/items/1'], parsed_response['uris']
|
142
|
-
assert_equal 3, parsed_response['total']
|
143
|
-
end
|
144
|
-
|
145
|
-
should "accept an offset parameter" do
|
146
|
-
response = @request.get('/items?offset=1', VALID_TEST_AUTH)
|
147
|
-
parsed_response = JSON.parse(response.body)
|
148
|
-
assert_equal ['/items/1', '/items/0'], parsed_response['uris']
|
149
|
-
assert_equal 1, parsed_response['offset']
|
150
|
-
assert_equal 3, parsed_response['total']
|
151
|
-
end
|
152
|
-
|
153
|
-
should "accept combined limit and offset parameters" do
|
154
|
-
response = @request.get('/items?limit=1&offset=1', VALID_TEST_AUTH)
|
155
|
-
parsed_response = JSON.parse(response.body)
|
156
|
-
assert_equal ['/items/1'], parsed_response['uris']
|
157
|
-
assert_equal 1, parsed_response['offset']
|
158
|
-
assert_equal 3, parsed_response['total']
|
159
|
-
end
|
160
|
-
|
161
|
-
should "return an empty list if no resources are found" do
|
162
|
-
response = @request.get('/things', VALID_TEST_AUTH)
|
163
|
-
parsed_response = JSON.parse(response.body)
|
164
|
-
assert_equal [], parsed_response['uris']
|
165
|
-
assert_equal 0, parsed_response['total']
|
166
|
-
assert_equal 0, parsed_response['offset']
|
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
|
264
|
-
end
|
265
|
-
|
266
|
-
context "on GET /:collection/:id" do
|
267
|
-
|
268
|
-
setup 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
|
-
should "be successful" do
|
276
|
-
assert_equal 200, @response.status
|
277
|
-
end
|
278
|
-
|
279
|
-
should "return a document for valid owner-originated requests" do
|
280
|
-
data = JSON.parse(@response.body)
|
281
|
-
assert_equal 'that', data['this']
|
282
|
-
end
|
283
|
-
|
284
|
-
should "return a 404 if a document does not exist" do
|
285
|
-
response = @request.get('/items/nothing', VALID_TEST_AUTH)
|
286
|
-
assert_equal 404, response.status
|
287
|
-
end
|
288
|
-
|
289
|
-
should "return a Content-Type header" do
|
290
|
-
assert_equal 'application/json', @response['Content-Type']
|
291
|
-
end
|
292
|
-
|
293
|
-
should "return an ETag header" do
|
294
|
-
assert @response['ETag']
|
295
|
-
end
|
296
|
-
|
297
|
-
should "return a Last-Modified header" do
|
298
|
-
assert @response['Last-Modified']
|
299
|
-
end
|
300
|
-
|
301
|
-
should "not return documents for unauthorized users" do
|
302
|
-
response = @request.get('/items/abc', CLOUDKIT_AUTH_KEY => 'bogus')
|
303
|
-
assert_equal 404, response.status
|
304
|
-
end
|
305
|
-
|
306
|
-
should "return a versions link header" do
|
307
|
-
assert @response['Link']
|
308
|
-
assert @response['Link'].match("<http://example.org/items/abc/versions>; rel=\"http://joncrosby.me/cloudkit/1.0/rel/versions\"")
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
context "on GET /:collection/:id/versions" do
|
313
|
-
|
314
|
-
setup do
|
315
|
-
@etags = []
|
316
|
-
4.times do |i|
|
317
|
-
json = JSON.generate(:this => i)
|
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']
|
322
|
-
end
|
323
|
-
@response = @request.get(
|
324
|
-
'/items/abc/versions', {'HTTP_HOST' => 'example.org'}.merge(VALID_TEST_AUTH))
|
325
|
-
@parsed_response = JSON.parse(@response.body)
|
326
|
-
end
|
327
|
-
|
328
|
-
should "be successful" do
|
329
|
-
assert_equal 200, @response.status
|
330
|
-
end
|
331
|
-
|
332
|
-
should "be successful even if the current resource has been deleted" do
|
333
|
-
@request.delete('/items/abc', {'HTTP_IF_MATCH' => @etags.last}.merge(VALID_TEST_AUTH))
|
334
|
-
response = @request.get('/items/abc/versions', VALID_TEST_AUTH)
|
335
|
-
assert_equal 200, @response.status
|
336
|
-
parsed_response = JSON.parse(response.body)
|
337
|
-
assert_equal 4, parsed_response['uris'].size
|
338
|
-
end
|
339
|
-
|
340
|
-
should "return a list of URIs for all versions of a resource" do
|
341
|
-
uris = @parsed_response['uris']
|
342
|
-
assert uris
|
343
|
-
assert_equal 4, uris.size
|
344
|
-
end
|
345
|
-
|
346
|
-
should "return a 404 if the resource does not exist" do
|
347
|
-
response = @request.get('/items/nothing/versions', VALID_TEST_AUTH)
|
348
|
-
assert_equal 404, response.status
|
349
|
-
end
|
350
|
-
|
351
|
-
should "return a 404 for non-owner-originated requests" do
|
352
|
-
response = @request.get(
|
353
|
-
'/items/abc/versions', CLOUDKIT_AUTH_KEY => 'someoneelse')
|
354
|
-
assert_equal 404, response.status
|
355
|
-
end
|
356
|
-
|
357
|
-
should "sort descending on last_modified date" do
|
358
|
-
assert_equal(
|
359
|
-
['/items/abc'].concat(@etags[0..-2].reverse.map{|e| "/items/abc/versions/#{e}"}),
|
360
|
-
@parsed_response['uris'])
|
361
|
-
end
|
362
|
-
|
363
|
-
should "return the total number of uris" do
|
364
|
-
assert @parsed_response['total']
|
365
|
-
assert_equal 4, @parsed_response['total']
|
366
|
-
end
|
367
|
-
|
368
|
-
should "return the offset" do
|
369
|
-
assert @parsed_response['offset']
|
370
|
-
assert_equal 0, @parsed_response['offset']
|
371
|
-
end
|
372
|
-
|
373
|
-
should "return a Content-Type header" do
|
374
|
-
assert_equal 'application/json', @response['Content-Type']
|
375
|
-
end
|
376
|
-
|
377
|
-
should "return an ETag" do
|
378
|
-
assert @response['ETag']
|
379
|
-
end
|
380
|
-
|
381
|
-
should "return a Last-Modified date" do
|
382
|
-
assert @response['Last-Modified']
|
383
|
-
end
|
384
|
-
|
385
|
-
should "accept a limit parameter" do
|
386
|
-
response = @request.get('/items/abc/versions?limit=2', VALID_TEST_AUTH)
|
387
|
-
parsed_response = JSON.parse(response.body)
|
388
|
-
assert_equal ['/items/abc', "/items/abc/versions/#{@etags[-2]}"],
|
389
|
-
parsed_response['uris']
|
390
|
-
assert_equal 4, parsed_response['total']
|
391
|
-
end
|
392
|
-
|
393
|
-
should "accept an offset parameter" do
|
394
|
-
response = @request.get('/items/abc/versions?offset=1', VALID_TEST_AUTH)
|
395
|
-
parsed_response = JSON.parse(response.body)
|
396
|
-
assert_equal @etags.reverse[1..-1].map{|e| "/items/abc/versions/#{e}"},
|
397
|
-
parsed_response['uris']
|
398
|
-
assert_equal 1, parsed_response['offset']
|
399
|
-
assert_equal 4, parsed_response['total']
|
400
|
-
end
|
401
|
-
|
402
|
-
should "accept combined limit and offset parameters" do
|
403
|
-
response = @request.get('/items/abc/versions?limit=1&offset=1', VALID_TEST_AUTH)
|
404
|
-
parsed_response = JSON.parse(response.body)
|
405
|
-
assert_equal ["/items/abc/versions/#{@etags[-2]}"], parsed_response['uris']
|
406
|
-
assert_equal 1, parsed_response['offset']
|
407
|
-
assert_equal 4, parsed_response['total']
|
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
|
522
|
-
end
|
523
|
-
|
524
|
-
context "on GET /:collection/:id/versions/:etag" do
|
525
|
-
|
526
|
-
setup do
|
527
|
-
@etags = []
|
528
|
-
2.times do |i|
|
529
|
-
json = JSON.generate(:this => i)
|
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']
|
534
|
-
end
|
535
|
-
@response = @request.get(
|
536
|
-
"/items/abc/versions/#{@etags.first}", VALID_TEST_AUTH)
|
537
|
-
@parsed_response = JSON.parse(@response.body)
|
538
|
-
end
|
539
|
-
|
540
|
-
should "be successful" do
|
541
|
-
assert_equal 200, @response.status
|
542
|
-
end
|
543
|
-
|
544
|
-
should "return a document for valid owner-originated requests" do
|
545
|
-
assert_equal 0, @parsed_response['this']
|
546
|
-
end
|
547
|
-
|
548
|
-
should "return a 404 if a document is not found" do
|
549
|
-
response = @request.get(
|
550
|
-
"/items/nothing/versions/#{@etags.first}", VALID_TEST_AUTH)
|
551
|
-
assert_equal 404, response.status
|
552
|
-
end
|
553
|
-
|
554
|
-
should "return a Content-Type header" do
|
555
|
-
assert_equal 'application/json', @response['Content-Type']
|
556
|
-
end
|
557
|
-
|
558
|
-
should "return an ETag header" do
|
559
|
-
assert @response['ETag']
|
560
|
-
end
|
561
|
-
|
562
|
-
should "return a Last-Modified header" do
|
563
|
-
assert @response['Last-Modified']
|
564
|
-
end
|
565
|
-
|
566
|
-
should "not return documents for unauthorized users" do
|
567
|
-
response = @request.get(
|
568
|
-
"/items/abc/versions/#{@etags.first}", CLOUDKIT_AUTH_KEY => 'someoneelse')
|
569
|
-
assert_equal 404, response.status
|
570
|
-
end
|
571
|
-
end
|
572
|
-
|
573
|
-
context "on POST /:collection" do
|
574
|
-
|
575
|
-
setup do
|
576
|
-
json = JSON.generate(:this => 'that')
|
577
|
-
@response = @request.post(
|
578
|
-
'/items', {:input => json}.merge(VALID_TEST_AUTH))
|
579
|
-
@body = JSON.parse(@response.body)
|
580
|
-
end
|
581
|
-
|
582
|
-
should "store the document" do
|
583
|
-
result = @request.get(@body['uri'], VALID_TEST_AUTH)
|
584
|
-
assert_equal 200, result.status
|
585
|
-
end
|
586
|
-
|
587
|
-
should "return a 201 when successful" do
|
588
|
-
assert_equal 201, @response.status
|
589
|
-
end
|
590
|
-
|
591
|
-
should "return the metadata" do
|
592
|
-
assert_equal 4, @body.keys.size
|
593
|
-
assert_same_elements ['ok', 'uri', 'etag', 'last_modified'], @body.keys
|
594
|
-
end
|
595
|
-
|
596
|
-
should "set the Content-Type header" do
|
597
|
-
assert_equal 'application/json', @response['Content-Type']
|
598
|
-
end
|
599
|
-
|
600
|
-
should "not set an ETag header" do
|
601
|
-
assert_nil @response['ETag']
|
602
|
-
end
|
603
|
-
|
604
|
-
should "not set a Last-Modified header" do
|
605
|
-
assert_nil @response['Last-Modified']
|
606
|
-
end
|
607
|
-
|
608
|
-
should "return a 422 if parsing fails" do
|
609
|
-
response = @request.post('/items', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
610
|
-
assert_equal 422, response.status
|
611
|
-
end
|
612
|
-
|
613
|
-
should "insert into its views" do
|
614
|
-
view = CloudKit::ExtractionView.new(
|
615
|
-
:fruits,
|
616
|
-
:observe => :items,
|
617
|
-
:extract => [:apple, :lemon])
|
618
|
-
store = CloudKit::Store.new(
|
619
|
-
:collections => [:items],
|
620
|
-
:views => [view])
|
621
|
-
json = JSON.generate(:apple => 'green')
|
622
|
-
store.put('/items/123', :json => json)
|
623
|
-
json = JSON.generate(:apple => 'red')
|
624
|
-
store.put('/items/456', :json => json)
|
625
|
-
result = store.get('/fruits', :apple => 'green')
|
626
|
-
uris = result.parsed_content['uris']
|
627
|
-
assert_equal 1, uris.size
|
628
|
-
assert uris.include?('/items/123')
|
629
|
-
end
|
630
|
-
end
|
631
|
-
|
632
|
-
context "on PUT /:collection/:id" do
|
633
|
-
|
634
|
-
setup do
|
635
|
-
json = JSON.generate(:this => 'that')
|
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')
|
640
|
-
@response = @request.put(
|
641
|
-
'/items/abc',
|
642
|
-
:input => json,
|
643
|
-
'HTTP_IF_MATCH' => etag,
|
644
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
645
|
-
@json = JSON.parse(@response.body)
|
646
|
-
end
|
647
|
-
|
648
|
-
should "create a document if it does not already exist" do
|
649
|
-
json = JSON.generate(:this => 'thing')
|
650
|
-
response = @request.put(
|
651
|
-
'/items/xyz', {:input => json}.merge(VALID_TEST_AUTH))
|
652
|
-
assert_equal 201, response.status
|
653
|
-
result = @request.get('/items/xyz', VALID_TEST_AUTH)
|
654
|
-
assert_equal 200, result.status
|
655
|
-
assert_equal 'thing', JSON.parse(result.body)['this']
|
656
|
-
end
|
657
|
-
|
658
|
-
should "not create new resources using deleted resource URIs" do
|
659
|
-
# This situation occurs when a stale client attempts to update
|
660
|
-
# a resource that has been removed. This test verifies that CloudKit
|
661
|
-
# does not attempt to create a new item with a URI equal to the
|
662
|
-
# removed item.
|
663
|
-
etag = JSON.parse(@response.body)['etag'];
|
664
|
-
@request.delete(
|
665
|
-
'/items/abc',
|
666
|
-
'HTTP_IF_MATCH' => etag,
|
667
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
668
|
-
json = JSON.generate(:foo => 'bar')
|
669
|
-
response = @request.put(
|
670
|
-
'/items/abc',
|
671
|
-
:input => json,
|
672
|
-
'HTTP_IF_MATCH' => etag,
|
673
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
674
|
-
assert_equal 410, response.status
|
675
|
-
end
|
676
|
-
|
677
|
-
should "update the document if it already exists" do
|
678
|
-
assert_equal 200, @response.status
|
679
|
-
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
680
|
-
assert_equal 'other', JSON.parse(result.body)['this']
|
681
|
-
end
|
682
|
-
|
683
|
-
should "return the metadata" do
|
684
|
-
assert_equal 4, @json.keys.size
|
685
|
-
assert_same_elements ['ok', 'uri', 'etag', 'last_modified'], @json.keys
|
686
|
-
end
|
687
|
-
|
688
|
-
should "set the Content-Type header" do
|
689
|
-
assert_equal 'application/json', @response['Content-Type']
|
690
|
-
end
|
691
|
-
|
692
|
-
should "not set an ETag header" do
|
693
|
-
assert_nil @response['ETag']
|
694
|
-
end
|
695
|
-
|
696
|
-
should "not set a Last-Modified header" do
|
697
|
-
assert_nil @response['Last-Modified']
|
698
|
-
end
|
699
|
-
|
700
|
-
should "not allow a remote_user change" do
|
701
|
-
json = JSON.generate(:this => 'other')
|
702
|
-
response = @request.put(
|
703
|
-
'/items/abc',
|
704
|
-
:input => json,
|
705
|
-
'HTTP_IF_MATCH' => @json['etag'],
|
706
|
-
CLOUDKIT_AUTH_KEY => 'someone_else')
|
707
|
-
assert_equal 404, response.status
|
708
|
-
end
|
709
|
-
|
710
|
-
should "detect and return conflicts" do
|
711
|
-
client_a_input = JSON.generate(:this => 'updated')
|
712
|
-
client_b_input = JSON.generate(:other => 'thing')
|
713
|
-
response = @request.put(
|
714
|
-
'/items/abc',
|
715
|
-
:input => client_a_input,
|
716
|
-
'HTTP_IF_MATCH' => @json['etag'],
|
717
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
718
|
-
assert_equal 200, response.status
|
719
|
-
response = @request.put(
|
720
|
-
'/items/abc',
|
721
|
-
:input => client_b_input,
|
722
|
-
'HTTP_IF_MATCH' => @json['etag'],
|
723
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
724
|
-
assert_equal 412, response.status
|
725
|
-
end
|
726
|
-
|
727
|
-
should "require an ETag for updates" do
|
728
|
-
json = JSON.generate(:this => 'updated')
|
729
|
-
response = @request.put(
|
730
|
-
'/items/abc',
|
731
|
-
{:input => json}.merge(VALID_TEST_AUTH))
|
732
|
-
assert_equal 400, response.status
|
733
|
-
end
|
734
|
-
|
735
|
-
should "return a 422 if parsing fails" do
|
736
|
-
response = @request.put(
|
737
|
-
'/items/zzz', {:input => 'fail'}.merge(VALID_TEST_AUTH))
|
738
|
-
assert_equal 422, response.status
|
739
|
-
end
|
740
|
-
|
741
|
-
should "version document updates" do
|
742
|
-
json = JSON.generate(:this => 'updated')
|
743
|
-
response = @request.put(
|
744
|
-
'/items/abc',
|
745
|
-
:input => json,
|
746
|
-
'HTTP_IF_MATCH' => @json['etag'],
|
747
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
748
|
-
assert_equal 200, response.status
|
749
|
-
etag = JSON.parse(response.body)['etag']
|
750
|
-
json = JSON.generate(:this => 'updated again')
|
751
|
-
new_response = @request.put(
|
752
|
-
'/items/abc',
|
753
|
-
:input => json,
|
754
|
-
'HTTP_IF_MATCH' => etag,
|
755
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
756
|
-
assert_equal 200, new_response.status
|
757
|
-
new_etag = JSON.parse(new_response.body)['etag']
|
758
|
-
assert_not_equal etag, new_etag
|
759
|
-
end
|
760
|
-
|
761
|
-
should "update its views" do
|
762
|
-
view = CloudKit::ExtractionView.new(
|
763
|
-
:fruits,
|
764
|
-
:observe => :items,
|
765
|
-
:extract => [:apple, :lemon])
|
766
|
-
store = CloudKit::Store.new(
|
767
|
-
:collections => [:items],
|
768
|
-
:views => [view])
|
769
|
-
json = JSON.generate(:apple => 'green')
|
770
|
-
result = store.put('/items/123', :json => json)
|
771
|
-
json = JSON.generate(:apple => 'red')
|
772
|
-
store.put(
|
773
|
-
'/items/123', :etag => result.parsed_content['etag'], :json => json)
|
774
|
-
result = store.get('/fruits', :apple => 'green')
|
775
|
-
uris = result.parsed_content['uris']
|
776
|
-
assert_equal 0, uris.size
|
777
|
-
result = store.get('/fruits', :apple => 'red')
|
778
|
-
uris = result.parsed_content['uris']
|
779
|
-
assert_equal 1, uris.size
|
780
|
-
assert uris.include?('/items/123')
|
781
|
-
end
|
782
|
-
end
|
783
|
-
|
784
|
-
context "on DELETE /:collection/:id" do
|
785
|
-
|
786
|
-
setup do
|
787
|
-
json = JSON.generate(:this => 'that')
|
788
|
-
@result = @request.put(
|
789
|
-
'/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
790
|
-
@etag = JSON.parse(@result.body)['etag']
|
791
|
-
end
|
792
|
-
|
793
|
-
should "delete the document" do
|
794
|
-
response = @request.delete(
|
795
|
-
'/items/abc',
|
796
|
-
'HTTP_IF_MATCH' => @etag,
|
797
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
798
|
-
assert_equal 200, response.status
|
799
|
-
result = @request.get('/items/abc', VALID_TEST_AUTH)
|
800
|
-
assert_equal 410, result.status
|
801
|
-
end
|
802
|
-
|
803
|
-
should "return the metadata" do
|
804
|
-
response = @request.delete(
|
805
|
-
'/items/abc',
|
806
|
-
'HTTP_IF_MATCH' => @etag,
|
807
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
808
|
-
json = JSON.parse(response.body)
|
809
|
-
assert_equal 4, json.keys.size
|
810
|
-
assert_same_elements ['ok', 'uri', 'etag', 'last_modified'], json.keys
|
811
|
-
end
|
812
|
-
|
813
|
-
should "set the Content-Type header" do
|
814
|
-
response = @request.delete(
|
815
|
-
'/items/abc',
|
816
|
-
'HTTP_IF_MATCH' => @etag,
|
817
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
818
|
-
assert_equal 'application/json', response['Content-Type']
|
819
|
-
end
|
820
|
-
|
821
|
-
should "not set an ETag header" do
|
822
|
-
response = @request.delete(
|
823
|
-
'/items/abc',
|
824
|
-
'HTTP_IF_MATCH' => @etag,
|
825
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
826
|
-
assert_nil response['ETag']
|
827
|
-
end
|
828
|
-
|
829
|
-
should "not set a Last-Modified header" do
|
830
|
-
response = @request.delete(
|
831
|
-
'/items/abc',
|
832
|
-
'HTTP_IF_MATCH' => @etag,
|
833
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
834
|
-
assert_nil response['Last-Modified']
|
835
|
-
end
|
836
|
-
|
837
|
-
should "return a 404 for items that have never existed" do
|
838
|
-
response = @request.delete(
|
839
|
-
'/items/zzz',
|
840
|
-
'HTTP_IF_MATCH' => @etag,
|
841
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
842
|
-
assert_equal 404, response.status
|
843
|
-
end
|
844
|
-
|
845
|
-
should "require an ETag" do
|
846
|
-
response = @request.delete(
|
847
|
-
'/items/abc',
|
848
|
-
VALID_TEST_AUTH)
|
849
|
-
assert_equal 400, response.status
|
850
|
-
end
|
851
|
-
|
852
|
-
should "verify the user in the doc" do
|
853
|
-
response = @request.delete(
|
854
|
-
'/items/abc',
|
855
|
-
'HTTP_IF_MATCH' => @etag,
|
856
|
-
CLOUDKIT_AUTH_KEY => 'someoneelse')
|
857
|
-
assert_equal 404, response.status
|
858
|
-
end
|
859
|
-
|
860
|
-
should "detect and return conflicts" do
|
861
|
-
json = JSON.generate(:this => 'that')
|
862
|
-
result = @request.put(
|
863
|
-
'/items/123', {:input => json}.merge(VALID_TEST_AUTH))
|
864
|
-
etag = JSON.parse(result.body)['etag']
|
865
|
-
client_a_input = JSON.generate(:this => 'updated')
|
866
|
-
client_b_input = JSON.generate(:other => 'thing')
|
867
|
-
response = @request.put(
|
868
|
-
'/items/123',
|
869
|
-
:input => client_a_input,
|
870
|
-
'HTTP_IF_MATCH' => etag,
|
871
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
872
|
-
assert_equal 200, response.status
|
873
|
-
response = @request.delete(
|
874
|
-
'/items/123',
|
875
|
-
:input => client_b_input,
|
876
|
-
'HTTP_IF_MATCH' => etag,
|
877
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
878
|
-
assert_equal 412, response.status
|
879
|
-
end
|
880
|
-
|
881
|
-
should "retain version history" do
|
882
|
-
response = @request.delete(
|
883
|
-
'/items/abc',
|
884
|
-
'HTTP_IF_MATCH' => @etag,
|
885
|
-
CLOUDKIT_AUTH_KEY => TEST_REMOTE_USER)
|
886
|
-
assert_equal 200, response.status
|
887
|
-
response = @request.get(
|
888
|
-
'/items/abc/versions',
|
889
|
-
VALID_TEST_AUTH)
|
890
|
-
json = JSON.parse(response.body)
|
891
|
-
assert_equal 1, json['total']
|
892
|
-
end
|
893
|
-
|
894
|
-
should "remove records from its views" do
|
895
|
-
view = CloudKit::ExtractionView.new(
|
896
|
-
:fruits,
|
897
|
-
:observe => :items,
|
898
|
-
:extract => [:apple, :lemon])
|
899
|
-
store = CloudKit::Store.new(
|
900
|
-
:collections => [:items],
|
901
|
-
:views => [view])
|
902
|
-
json = JSON.generate(:apple => 'green')
|
903
|
-
result = store.put('/items/123', :json => json)
|
904
|
-
store.delete('/items/123', :etag => result.parsed_content['etag'])
|
905
|
-
result = store.get('/fruits', :apple => 'green')
|
906
|
-
uris = result.parsed_content['uris']
|
907
|
-
assert_equal [], uris
|
908
|
-
end
|
909
|
-
end
|
910
|
-
|
911
|
-
context "on OPTIONS /:collection" do
|
912
|
-
|
913
|
-
setup do
|
914
|
-
@response = @request.request('OPTIONS', '/items', VALID_TEST_AUTH)
|
915
|
-
end
|
916
|
-
|
917
|
-
should "return a 200 status" do
|
918
|
-
assert_equal 200, @response.status
|
919
|
-
end
|
920
|
-
|
921
|
-
should "return a list of available methods" do
|
922
|
-
assert @response['Allow']
|
923
|
-
methods = @response['Allow'].split(', ')
|
924
|
-
assert_same_elements(['GET', 'POST', 'HEAD', 'OPTIONS'], methods)
|
925
|
-
end
|
926
|
-
end
|
927
|
-
|
928
|
-
context "on OPTIONS /:collection/_resolved" do
|
929
|
-
end
|
930
|
-
|
931
|
-
context "on OPTIONS /:collection/:id" do
|
932
|
-
|
933
|
-
setup do
|
934
|
-
@response = @request.request('OPTIONS', '/items/xyz', VALID_TEST_AUTH)
|
935
|
-
end
|
936
|
-
|
937
|
-
should "return a 200 status" do
|
938
|
-
assert_equal 200, @response.status
|
939
|
-
end
|
940
|
-
|
941
|
-
should "return a list of available methods" do
|
942
|
-
assert @response['Allow']
|
943
|
-
methods = @response['Allow'].split(', ')
|
944
|
-
assert_same_elements(['GET', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], methods)
|
945
|
-
end
|
946
|
-
end
|
947
|
-
|
948
|
-
context "on OPTIONS /:collection/:id/versions" do
|
949
|
-
end
|
950
|
-
|
951
|
-
context "on OPTIONS /:collection/:id/versions/_resolved" do
|
952
|
-
end
|
953
|
-
|
954
|
-
context "on OPTIONS /:collection/:id/versions/:etag" do
|
955
|
-
end
|
956
|
-
|
957
|
-
context "on HEAD" do
|
958
|
-
|
959
|
-
should "return an empty body" do
|
960
|
-
json = JSON.generate(:this => 'that')
|
961
|
-
@request.put('/items/abc', {:input => json}.merge(VALID_TEST_AUTH))
|
962
|
-
response = @request.request('HEAD', '/items/abc', VALID_TEST_AUTH)
|
963
|
-
assert_equal '', response.body
|
964
|
-
response = @request.request('HEAD', '/items', VALID_TEST_AUTH)
|
965
|
-
assert_equal '', response.body
|
966
|
-
end
|
967
|
-
|
968
|
-
end
|
969
|
-
end
|
970
|
-
end
|
971
|
-
end
|