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,64 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "An OpenIDFilter" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
openid_app = Rack::Builder.new {
|
7
|
+
use Rack::Lint
|
8
|
+
use Rack::Session::Pool
|
9
|
+
use CloudKit::OpenIDFilter, :allow => ['/foo']
|
10
|
+
run echo_env(CLOUDKIT_AUTH_KEY)
|
11
|
+
}
|
12
|
+
@request = Rack::MockRequest.new(openid_app)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow root url pass through" do
|
16
|
+
response = @request.get('/')
|
17
|
+
response.status.should == 200
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow pass through of URIs defined in :allow" do
|
21
|
+
response = @request.get('/foo')
|
22
|
+
response.status.should == 200
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should redirect to the login page if authorization is required" do
|
26
|
+
response = @request.get('/protected')
|
27
|
+
response.status.should == 302
|
28
|
+
response['Location'].should == '/login'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should notify downstream nodes of its presence" do
|
32
|
+
app = Rack::Builder.new do
|
33
|
+
use Rack::Session::Pool
|
34
|
+
use CloudKit::OpenIDFilter
|
35
|
+
run echo_env(CLOUDKIT_VIA)
|
36
|
+
end
|
37
|
+
response = Rack::MockRequest.new(app).get('/')
|
38
|
+
response.body.should == CLOUDKIT_OPENID_FILTER_KEY
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with upstream authorization middleware" do
|
42
|
+
|
43
|
+
it "should allow pass through if the auth env variable is populated" do
|
44
|
+
response = @request.get('/protected', VALID_TEST_AUTH)
|
45
|
+
response.status.should == 200
|
46
|
+
response.body.should == TEST_REMOTE_USER
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return the auth challenge header" do
|
50
|
+
response = @request.get('/protected',
|
51
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
52
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
53
|
+
response['WWW-Authenticate'].should_not be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return a 401 status if authorization is required" do
|
57
|
+
response = @request.get('/protected',
|
58
|
+
CLOUDKIT_VIA => CLOUDKIT_OAUTH_FILTER_KEY,
|
59
|
+
CLOUDKIT_AUTH_CHALLENGE => {'WWW-Authenticate' => 'etc.'})
|
60
|
+
response.status.should == 401
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "An OpenIDStore" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
7
|
+
@store = CloudKit::OpenIDStore.new
|
8
|
+
@server = 'http://openid.claimid.com/server'
|
9
|
+
@handle = "{HMAC-SHA1}{736kwv3j}{wbhwEK==}"
|
10
|
+
@secret = "\350\068\753\436\567\8327\232\241\025\254\3117&\016\031\355#sV"
|
11
|
+
@issued = Time.now
|
12
|
+
@lifetime = 120960
|
13
|
+
@type = "HMAC-SHA1"
|
14
|
+
@association = OpenID::Association.new(@handle, @secret, @issued, @lifetime, @type)
|
15
|
+
@store.store_association(@server, @association)
|
16
|
+
@result = CloudKit::Resource.all.first.parsed_json
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:each) do
|
20
|
+
CloudKit.storage_adapter.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should know its version" do
|
24
|
+
@store.version.should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when storing an association" do
|
28
|
+
|
29
|
+
it "should base64 encode the handle" do
|
30
|
+
@result['handle'].should == Base64.encode64(@handle)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should base64 encode the secret" do
|
34
|
+
@result['secret'].should == Base64.encode64(@secret)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should convert the issue time to an integer" do
|
38
|
+
@result['issued'].should == @issued.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should store the lifetime as given" do
|
42
|
+
@result['lifetime'].should == @lifetime
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should store the association type as given" do
|
46
|
+
@result['assoc_type'].should == @type
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should remove previous associations with the given server_url and association handle" do
|
50
|
+
association = OpenID::Association.new(@handle, @secret, @issued, @lifetime, @type)
|
51
|
+
@store.store_association(@server, association)
|
52
|
+
associations = CloudKit::Resource.current(
|
53
|
+
:collection_reference => "/cloudkit_openid_associations")
|
54
|
+
associations.size.should == 1
|
55
|
+
result = associations.first.parsed_json
|
56
|
+
result['secret'].should == Base64.encode64(@secret)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "when removing an association" do
|
61
|
+
|
62
|
+
it "should succeed" do
|
63
|
+
@store.remove_association(@server, @association.handle)
|
64
|
+
associations = CloudKit::Resource.current(
|
65
|
+
:collection_reference => "/cloudkit_openid_associations")
|
66
|
+
associations.size.should == 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when finding an association" do
|
71
|
+
|
72
|
+
it "should return the correct object" do
|
73
|
+
association = @store.get_association(@server, @association.handle)
|
74
|
+
association.should == @association
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when using a nonce" do
|
79
|
+
|
80
|
+
before(:each) do
|
81
|
+
@time = Time.now.to_i
|
82
|
+
@salt = 'salt'
|
83
|
+
@store.use_nonce(@server, @time, @salt)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should store the nonce" do
|
87
|
+
nonce = CloudKit::Resource.first(
|
88
|
+
:collection_reference => "/cloudkit_openid_nonces",
|
89
|
+
:deleted => false)
|
90
|
+
nonce.should_not be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should reject the nonce if it has already been used" do
|
94
|
+
@store.use_nonce(@server, @time, @salt).should_not be_nil
|
95
|
+
nonces = CloudKit::Resource.all(
|
96
|
+
:collection_reference => "/cloudkit_openid_nonces",
|
97
|
+
:deleted => false)
|
98
|
+
nonces.size.should == 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "Rack::Builder" do
|
4
|
+
|
5
|
+
it "should expose services" do
|
6
|
+
app = Rack::Builder.new do
|
7
|
+
expose :items, :things
|
8
|
+
run lambda {|app| [200, {}, ['hello']]}
|
9
|
+
end
|
10
|
+
response = Rack::MockRequest.new(app).get('/items')
|
11
|
+
response.status.should == 200
|
12
|
+
documents = JSON.parse(response.body)['uris']
|
13
|
+
documents.should == []
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should expose services with auth using 'contain'" do
|
17
|
+
app = Rack::Builder.new do
|
18
|
+
contain :items, :things
|
19
|
+
run lambda {|app| [200, {}, ['hello']]}
|
20
|
+
end
|
21
|
+
response = Rack::MockRequest.new(app).get('/items')
|
22
|
+
response.status.should == 401
|
23
|
+
response = Rack::MockRequest.new(app).get('/things')
|
24
|
+
response.status.should == 401
|
25
|
+
response = Rack::MockRequest.new(app).get('/')
|
26
|
+
response.status.should == 200
|
27
|
+
response.body.should == 'hello'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should insert a default app if one does not exist" do
|
31
|
+
app = Rack::Builder.new { contain :items }
|
32
|
+
response = Rack::MockRequest.new(app).get('/items')
|
33
|
+
response.status.should == 401
|
34
|
+
response = Rack::MockRequest.new(app).get('/')
|
35
|
+
response.status.should == 200
|
36
|
+
response.body.match('CloudKit').should_not be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A Request" do
|
4
|
+
|
5
|
+
it "should match requests with routes" do
|
6
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
7
|
+
'http://example.com')).match?('GET', '/').should be_true
|
8
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
9
|
+
'http://example.com/')).match?('GET', '/').should be_true
|
10
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
11
|
+
'http://example.com/')).match?('POST', '/').should_not be_true
|
12
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
13
|
+
'http://example.com/hello')).match?('GET', '/hello').should be_true
|
14
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
15
|
+
'http://example.com/hello')).match?('GET', '/hello').should be_true
|
16
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
17
|
+
'http://example.com/hello', :method => 'POST')).match?(
|
18
|
+
'POST', '/hello').should be_true
|
19
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
20
|
+
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
21
|
+
'POST', '/hello', [{'q' => 'a'}]).should be_true
|
22
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
23
|
+
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
24
|
+
'POST', '/hello', ['q']).should be_true
|
25
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
26
|
+
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
27
|
+
'POST', '/hello', [{'q' => 'b'}]).should_not be_true
|
28
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
29
|
+
'http://example.com/hello?q', :method => 'POST')).match?(
|
30
|
+
'POST', '/hello', [{'q' => nil}]).should be_true
|
31
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
32
|
+
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
33
|
+
'POST', '/hello', [{'q' => nil}]).should_not be_true
|
34
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
35
|
+
'http://example.com/hello?q=a', :method => 'POST')).match?(
|
36
|
+
'POST', '/hello', [{'q' => ''}]).should_not be_true
|
37
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
38
|
+
'http://example.com/hello?q&x=y', :method => 'PUT')).match?(
|
39
|
+
'PUT', '/hello', ['q', {'x' => 'y'}]).should be_true
|
40
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
41
|
+
'http://example.com/hello?q&x=y&z', :method => 'PUT')).match?(
|
42
|
+
'PUT', '/hello', ['q', {'x' => 'y'}]).should be_true
|
43
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
44
|
+
'http://example.com/hello?q&x=y', :method => 'PUT')).match?(
|
45
|
+
'PUT', '/hello', [{'q' => 'a'},{'x' => 'y'}]).should_not be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should treat a trailing :id as a wildcard for path matching" do
|
49
|
+
CloudKit::Request.new(Rack::MockRequest.env_for(
|
50
|
+
'http://example.com/hello/123')).match?('GET', '/hello/:id').should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should inject stack-internal via-style env vars" do
|
54
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/test'))
|
55
|
+
request.via.should == []
|
56
|
+
request.inject_via('a.b')
|
57
|
+
request.via.include?('a.b').should be_true
|
58
|
+
request.inject_via('c.d')
|
59
|
+
request.via.include?('a.b').should be_true
|
60
|
+
request.via.include?('c.d').should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should announce the use of auth middleware" do
|
64
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
65
|
+
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
66
|
+
request.via.include?(CLOUDKIT_OAUTH_FILTER_KEY).should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should know if auth provided by upstream middleware" do
|
70
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
71
|
+
request.announce_auth(CLOUDKIT_OAUTH_FILTER_KEY)
|
72
|
+
request.using_auth?.should be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should know the current user" do
|
76
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
77
|
+
request.current_user.should be_nil
|
78
|
+
request = CloudKit::Request.new(
|
79
|
+
Rack::MockRequest.env_for('/', CLOUDKIT_AUTH_KEY => 'cecil'))
|
80
|
+
request.current_user.should_not be_nil
|
81
|
+
request.current_user.should == 'cecil'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should set the current user" do
|
85
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
86
|
+
request.current_user = 'cecil'
|
87
|
+
request.current_user.should_not be_nil
|
88
|
+
request.current_user.should == 'cecil'
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should know the login url" do
|
92
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
93
|
+
request.login_url.should == '/login'
|
94
|
+
request = CloudKit::Request.new(
|
95
|
+
Rack::MockRequest.env_for('/', CLOUDKIT_LOGIN_URL => '/sessions'))
|
96
|
+
request.login_url.should == '/sessions'
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should set the login url" do
|
100
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
101
|
+
request.login_url = '/welcome'
|
102
|
+
request.login_url.should == '/welcome'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should know the logout url" do
|
106
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
107
|
+
request.logout_url.should == '/logout'
|
108
|
+
request = CloudKit::Request.new(
|
109
|
+
Rack::MockRequest.env_for('/', CLOUDKIT_LOGOUT_URL => '/sessions'))
|
110
|
+
request.logout_url.should == '/sessions'
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should set the logout url" do
|
114
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
115
|
+
request.logout_url = '/goodbye'
|
116
|
+
request.logout_url.should == '/goodbye'
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should get the session" do
|
120
|
+
request = CloudKit::Request.new(
|
121
|
+
Rack::MockRequest.env_for('/', 'rack.session' => 'this'))
|
122
|
+
request.session.should_not be_nil
|
123
|
+
request.session.should == 'this'
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should know the flash" do
|
127
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
128
|
+
'/', 'rack.session' => {}))
|
129
|
+
request.flash.is_a?(CloudKit::FlashSession).should be_true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should parse if-match headers" do
|
133
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
134
|
+
'/items/123/versions'))
|
135
|
+
request.if_match.should be_nil
|
136
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
137
|
+
'/items/123/versions', 'HTTP_IF_MATCH' => '"a"'))
|
138
|
+
request.if_match.should == 'a'
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should treat a list of etags in an if-match header as a single etag" do
|
142
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
143
|
+
'/items/123/versions', 'HTTP_IF_MATCH' => '"a", "b"'))
|
144
|
+
# See CloudKit::Request#if_match for more info on this expectation
|
145
|
+
request.if_match.should == 'a", "b'
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should ignore if-match when set to *" do
|
149
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
150
|
+
'/items/123/versions', 'HTTP_IF_MATCH' => '*'))
|
151
|
+
request.if_match.should be_nil
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should understand header auth" do
|
155
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for(
|
156
|
+
'http://photos.example.net/photos?file=vacation.jpg&size=original',
|
157
|
+
'Authorization' =>
|
158
|
+
'OAuth realm="",' +
|
159
|
+
'oauth_version="1.0",' +
|
160
|
+
'oauth_consumer_key="dpf43f3p2l4k3l03",' +
|
161
|
+
'oauth_token="nnch734d00sl2jdk",' +
|
162
|
+
'oauth_timestamp="1191242096",' +
|
163
|
+
'oauth_nonce="kllo9940pd9333jh",' +
|
164
|
+
'oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",' +
|
165
|
+
'oauth_signature_method="HMAC-SHA1"'))
|
166
|
+
request['oauth_consumer_key'].should == 'dpf43f3p2l4k3l03'
|
167
|
+
request['oauth_token'].should == 'nnch734d00sl2jdk'
|
168
|
+
request['oauth_timestamp'].should == '1191242096'
|
169
|
+
request['oauth_nonce'].should == 'kllo9940pd9333jh'
|
170
|
+
request['oauth_signature'].should == 'tR3+Ty81lMeYAr/Fid0kMTYa/WM='
|
171
|
+
request['oauth_signature_method'].should == 'HMAC-SHA1'
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should know the last path element" do
|
175
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/'))
|
176
|
+
request.last_path_element.should be_nil
|
177
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc'))
|
178
|
+
request.last_path_element.should == 'abc'
|
179
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc/'))
|
180
|
+
request.last_path_element.should == 'abc'
|
181
|
+
request = CloudKit::Request.new(Rack::MockRequest.env_for('/abc/def'))
|
182
|
+
request.last_path_element.should == 'def'
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A Resource" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
CloudKit.storage_adapter.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "on initialization" do
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
@resource = CloudKit::Resource.new(
|
17
|
+
CloudKit::URI.new('/items/123'),
|
18
|
+
JSON.generate({:foo => 'bar'}),
|
19
|
+
'http://eric.dolphy.info')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should know its uri" do
|
23
|
+
@resource.uri.string.should == CloudKit::URI.new('/items/123').string
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should know its json" do
|
27
|
+
@resource.json.should == "{\"foo\":\"bar\"}"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should know its remote user" do
|
31
|
+
@resource.remote_user.should == 'http://eric.dolphy.info'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should default its deleted status to false" do
|
35
|
+
@resource.should_not be_deleted
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should default its archived status to false" do
|
39
|
+
@resource.should_not be_archived
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should default its etag to nil" do
|
43
|
+
@resource.etag.should be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should default its last-modified date to nil" do
|
47
|
+
@resource.last_modified.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should know if it is current" do
|
51
|
+
@resource.should be_current
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "on save" do
|
57
|
+
|
58
|
+
before(:each) do
|
59
|
+
@resource = CloudKit::Resource.new(
|
60
|
+
CloudKit::URI.new('/items/123'),
|
61
|
+
JSON.generate({:foo => 'bar'}),
|
62
|
+
'http://eric.dolphy.info')
|
63
|
+
@resource.save
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should set its etag" do
|
67
|
+
@resource.etag.should_not be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should set its last modified date" do
|
71
|
+
@resource.last_modified.should_not be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should adjust its URI when adding to a resource collection" do
|
75
|
+
resource = CloudKit::Resource.new(
|
76
|
+
CloudKit::URI.new('/items'),
|
77
|
+
JSON.generate({:foo => 'bar'}),
|
78
|
+
'http://eric.dolphy.info')
|
79
|
+
resource.save
|
80
|
+
resource.uri.string.should_not == '/items'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should flatten its json structure for querying" do
|
84
|
+
hash = CloudKit.storage_adapter.query.first
|
85
|
+
hash.keys.include?('foo').should be_true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should know it is current" do
|
89
|
+
@resource.should be_current
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "on create" do
|
95
|
+
|
96
|
+
before(:each) do
|
97
|
+
resource = CloudKit::Resource.create(
|
98
|
+
CloudKit::URI.new('/items/123'),
|
99
|
+
JSON.generate({:foo => 'bar'}),
|
100
|
+
'http://eric.dolphy.info')
|
101
|
+
@result = CloudKit.storage_adapter.query { |q|
|
102
|
+
q.add_condition 'uri', :eql, '/items/123'
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should save the resource" do
|
107
|
+
@result.size.should == 1
|
108
|
+
@result.first['json'].should == "{\"foo\":\"bar\"}"
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "on update" do
|
114
|
+
|
115
|
+
before(:each) do
|
116
|
+
@resource = CloudKit::Resource.create(
|
117
|
+
CloudKit::URI.new('/items/123'),
|
118
|
+
JSON.generate({:foo => 'bar'}),
|
119
|
+
'http://eric.dolphy.info')
|
120
|
+
@original_resource = @resource.dup
|
121
|
+
now = Time.now
|
122
|
+
Time.stub!(:now).and_return(now+1)
|
123
|
+
@resource.update(JSON.generate({:foo => 'baz'}))
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should version the resource" do
|
127
|
+
@resource.versions.size.should == 2
|
128
|
+
@resource.versions[-1].should be_archived
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should set a new etag" do
|
132
|
+
@resource.etag.should_not == @original_resource.etag
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should set a new last modified date" do
|
136
|
+
@resource.last_modified.should_not == @original_resource.last_modified
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should fail on archived resource versions" do
|
140
|
+
lambda {
|
141
|
+
@resource.versions[-1].update({:foo => 'box'})
|
142
|
+
}.should raise_error(CloudKit::HistoricalIntegrityViolation)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should fail on deleted resource versions" do
|
146
|
+
lambda {
|
147
|
+
@resource.delete
|
148
|
+
@resource.update({:foo => 'box'})
|
149
|
+
}.should raise_error(CloudKit::HistoricalIntegrityViolation)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "on delete" do
|
155
|
+
|
156
|
+
before(:each) do
|
157
|
+
@resource = CloudKit::Resource.create(
|
158
|
+
CloudKit::URI.new('/items/123'),
|
159
|
+
JSON.generate({:foo => 'bar'}),
|
160
|
+
'http://eric.dolphy.info')
|
161
|
+
now = Time.now
|
162
|
+
Time.stub!(:now).and_return(now+1)
|
163
|
+
@resource.delete
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should version the resource" do
|
167
|
+
@resource.versions.size.should == 2
|
168
|
+
@resource.versions[-1].should be_archived
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should set the etag on the main resource to nil" do
|
172
|
+
@resource.etag.should be_nil
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should know it has been deleted" do
|
176
|
+
@resource.deleted?.should be_true
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should fail on archived resource versions" do
|
180
|
+
lambda {
|
181
|
+
@resource.versions[-1].update({:foo => 'box'})
|
182
|
+
}.should raise_error(CloudKit::HistoricalIntegrityViolation)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "with versions" do
|
188
|
+
|
189
|
+
before(:each) do
|
190
|
+
@resource = CloudKit::Resource.create(
|
191
|
+
CloudKit::URI.new('/items/123'),
|
192
|
+
JSON.generate({:foo => 'bar'}),
|
193
|
+
'http://eric.dolphy.info')
|
194
|
+
@resource_list = [@resource.dup]
|
195
|
+
|
196
|
+
2.times { |i|
|
197
|
+
now = Time.now
|
198
|
+
Time.stub!(:now).and_return(now+1)
|
199
|
+
@resource.update(JSON.generate({:foo => i}))
|
200
|
+
@resource_list << @resource.dup
|
201
|
+
}
|
202
|
+
@resource_list.reverse!
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should keep an ordered list of versions" do
|
206
|
+
@resource.versions.map { |version| version.last_modified }.
|
207
|
+
should == @resource_list.map { |version| version.last_modified }
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should include the current version in the version list" do
|
211
|
+
current_ts = @resource.last_modified
|
212
|
+
@resource_list.map { |version| version.last_modified }.should include(current_ts)
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should know its previous version" do
|
216
|
+
@resource.previous_version.last_modified.should == @resource_list[1].last_modified
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should know its previous versions" do
|
220
|
+
expected_times = @resource_list[1..-1].map { |version| version.last_modified }
|
221
|
+
@resource.previous_versions.map { |version| version.last_modified }.should == expected_times
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "when finding" do
|
227
|
+
|
228
|
+
before(:each) do
|
229
|
+
['bar', 'baz'].each do |value|
|
230
|
+
CloudKit::Resource.create(
|
231
|
+
CloudKit::URI.new('/items'),
|
232
|
+
JSON.generate({:foo => value}),
|
233
|
+
"http://eric.dolphy.info/#{value}_user")
|
234
|
+
end
|
235
|
+
CloudKit::Resource.create(
|
236
|
+
CloudKit::URI.new('/items'),
|
237
|
+
JSON.generate({:foo => 'box'}),
|
238
|
+
"http://eric.dolphy.info/bar_user")
|
239
|
+
end
|
240
|
+
|
241
|
+
describe "using #all" do
|
242
|
+
|
243
|
+
it "should find matching resources" do
|
244
|
+
result = CloudKit::Resource.all(
|
245
|
+
:remote_user => 'http://eric.dolphy.info/bar_user')
|
246
|
+
result.size.should == 2
|
247
|
+
result.map { |item| item.remote_user.should == 'http://eric.dolphy.info/bar_user' }
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should return all elements if no restrictions are given" do
|
251
|
+
CloudKit::Resource.all.size.should == 3
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should return an empty array if no resources are found" do
|
255
|
+
CloudKit::Resource.all(:uri => 'fail').should be_empty
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should find with query parameters referencing JSON elements" do
|
259
|
+
resources = CloudKit::Resource.all(
|
260
|
+
:collection_reference => '/items',
|
261
|
+
:foo => 'bar')
|
262
|
+
resources.size.should == 1
|
263
|
+
resources.first.json.should == "{\"foo\":\"bar\"}"
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
describe "on #first" do
|
269
|
+
|
270
|
+
it "should find the first matching resource" do
|
271
|
+
result = CloudKit::Resource.first(:remote_user => 'http://eric.dolphy.info/bar_user')
|
272
|
+
result.should_not === Array
|
273
|
+
result.remote_user.should == 'http://eric.dolphy.info/bar_user'
|
274
|
+
result.parsed_json['foo'].should == 'box' # all listings are reverse ordered
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
describe "on #current" do
|
280
|
+
|
281
|
+
it "should find only current matching resources" do
|
282
|
+
resource = CloudKit::Resource.first(:remote_user => 'http://eric.dolphy.info/bar_user')
|
283
|
+
resource.update(JSON.generate({:foo => 'x'}))
|
284
|
+
CloudKit::Resource.current(:collection_reference => '/items').size.should == 3
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|