cloudkit-jruby 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGES +47 -0
  2. data/COPYING +20 -0
  3. data/README +84 -0
  4. data/Rakefile +42 -0
  5. data/TODO +21 -0
  6. data/cloudkit.gemspec +89 -0
  7. data/doc/curl.html +388 -0
  8. data/doc/images/example-code.gif +0 -0
  9. data/doc/images/json-title.gif +0 -0
  10. data/doc/images/oauth-discovery-logo.gif +0 -0
  11. data/doc/images/openid-logo.gif +0 -0
  12. data/doc/index.html +90 -0
  13. data/doc/main.css +151 -0
  14. data/doc/rest-api.html +467 -0
  15. data/examples/1.ru +3 -0
  16. data/examples/2.ru +3 -0
  17. data/examples/3.ru +6 -0
  18. data/examples/4.ru +5 -0
  19. data/examples/5.ru +9 -0
  20. data/examples/6.ru +11 -0
  21. data/examples/TOC +17 -0
  22. data/lib/cloudkit.rb +92 -0
  23. data/lib/cloudkit/constants.rb +34 -0
  24. data/lib/cloudkit/exceptions.rb +10 -0
  25. data/lib/cloudkit/flash_session.rb +20 -0
  26. data/lib/cloudkit/oauth_filter.rb +266 -0
  27. data/lib/cloudkit/oauth_store.rb +48 -0
  28. data/lib/cloudkit/openid_filter.rb +236 -0
  29. data/lib/cloudkit/openid_store.rb +100 -0
  30. data/lib/cloudkit/rack/builder.rb +120 -0
  31. data/lib/cloudkit/rack/router.rb +20 -0
  32. data/lib/cloudkit/request.rb +177 -0
  33. data/lib/cloudkit/service.rb +162 -0
  34. data/lib/cloudkit/store.rb +349 -0
  35. data/lib/cloudkit/store/memory_table.rb +99 -0
  36. data/lib/cloudkit/store/resource.rb +269 -0
  37. data/lib/cloudkit/store/response.rb +52 -0
  38. data/lib/cloudkit/store/response_helpers.rb +84 -0
  39. data/lib/cloudkit/templates/authorize_request_token.erb +19 -0
  40. data/lib/cloudkit/templates/oauth_descriptor.erb +43 -0
  41. data/lib/cloudkit/templates/oauth_meta.erb +8 -0
  42. data/lib/cloudkit/templates/openid_login.erb +31 -0
  43. data/lib/cloudkit/templates/request_authorization.erb +23 -0
  44. data/lib/cloudkit/templates/request_token_denied.erb +18 -0
  45. data/lib/cloudkit/uri.rb +88 -0
  46. data/lib/cloudkit/user_store.rb +37 -0
  47. data/lib/cloudkit/util.rb +25 -0
  48. data/spec/ext_spec.rb +76 -0
  49. data/spec/flash_session_spec.rb +20 -0
  50. data/spec/memory_table_spec.rb +86 -0
  51. data/spec/oauth_filter_spec.rb +326 -0
  52. data/spec/oauth_store_spec.rb +10 -0
  53. data/spec/openid_filter_spec.rb +81 -0
  54. data/spec/openid_store_spec.rb +101 -0
  55. data/spec/rack_builder_spec.rb +39 -0
  56. data/spec/request_spec.rb +191 -0
  57. data/spec/resource_spec.rb +310 -0
  58. data/spec/service_spec.rb +1039 -0
  59. data/spec/spec_helper.rb +32 -0
  60. data/spec/store_spec.rb +10 -0
  61. data/spec/uri_spec.rb +93 -0
  62. data/spec/user_store_spec.rb +10 -0
  63. data/spec/util_spec.rb +11 -0
  64. metadata +180 -0
@@ -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,191 @@
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
+ it "should know its domain root" do
186
+ request = CloudKit::Request.new(Rack::MockRequest.env_for(
187
+ '/', 'HTTP_HOST' => 'example.com'))
188
+ request.domain_root.should == "http://example.com"
189
+ end
190
+
191
+ end
@@ -0,0 +1,310 @@
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
+ def store_json(hash)
97
+ CloudKit::Resource.create(
98
+ CloudKit::URI.new('/items/123'),
99
+ JSON.generate(hash),
100
+ 'http://eric.dolphy.info')
101
+ 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 = store_json({:foo => 'bar'})
108
+ result.size.should == 1
109
+ result.first['json'].should == "{\"foo\":\"bar\"}"
110
+ end
111
+
112
+ it "should accept nested array values" do
113
+ result = store_json({:foo => [1,2]})
114
+ result.size.should == 1
115
+ result.first['json'].should == '{"foo":[1,2]}'
116
+ end
117
+
118
+ it "should accept nested hash values" do
119
+ result = store_json({:foo => {:bar => 'baz'}})
120
+ result.size.should == 1
121
+ result.first['json'].should == '{"foo":{"bar":"baz"}}'
122
+ end
123
+
124
+ it "should accept recursively nested array/hash values" do
125
+ result = store_json({:foo => [1,{:bar => [2,3]}]})
126
+ result.size.should == 1
127
+ result.first['json'].should == '{"foo":[1,{"bar":[2,3]}]}'
128
+ end
129
+
130
+ end
131
+
132
+ describe "on update" do
133
+
134
+ before(:each) do
135
+ @resource = CloudKit::Resource.create(
136
+ CloudKit::URI.new('/items/123'),
137
+ JSON.generate({:foo => 'bar'}),
138
+ 'http://eric.dolphy.info')
139
+ @original_resource = @resource.dup
140
+ now = Time.now
141
+ Time.stub!(:now).and_return(now+1)
142
+ @resource.update(JSON.generate({:foo => 'baz'}))
143
+ end
144
+
145
+ it "should version the resource" do
146
+ @resource.versions.size.should == 2
147
+ @resource.versions[-1].should be_archived
148
+ end
149
+
150
+ it "should set a new etag" do
151
+ @resource.etag.should_not == @original_resource.etag
152
+ end
153
+
154
+ it "should set a new last modified date" do
155
+ @resource.last_modified.should_not == @original_resource.last_modified
156
+ end
157
+
158
+ it "should fail on archived resource versions" do
159
+ lambda {
160
+ @resource.versions[-1].update({:foo => 'box'})
161
+ }.should raise_error(CloudKit::HistoricalIntegrityViolation)
162
+ end
163
+
164
+ it "should fail on deleted resource versions" do
165
+ lambda {
166
+ @resource.delete
167
+ @resource.update({:foo => 'box'})
168
+ }.should raise_error(CloudKit::HistoricalIntegrityViolation)
169
+ end
170
+
171
+ end
172
+
173
+ describe "on delete" do
174
+
175
+ before(:each) do
176
+ @resource = CloudKit::Resource.create(
177
+ CloudKit::URI.new('/items/123'),
178
+ JSON.generate({:foo => 'bar'}),
179
+ 'http://eric.dolphy.info')
180
+ now = Time.now
181
+ Time.stub!(:now).and_return(now+1)
182
+ @resource.delete
183
+ end
184
+
185
+ it "should version the resource" do
186
+ @resource.versions.size.should == 2
187
+ @resource.versions[-1].should be_archived
188
+ end
189
+
190
+ it "should set the etag on the main resource to nil" do
191
+ @resource.etag.should be_nil
192
+ end
193
+
194
+ it "should know it has been deleted" do
195
+ @resource.deleted?.should be_true
196
+ end
197
+
198
+ it "should fail on archived resource versions" do
199
+ lambda {
200
+ @resource.versions[-1].update({:foo => 'box'})
201
+ }.should raise_error(CloudKit::HistoricalIntegrityViolation)
202
+ end
203
+
204
+ end
205
+
206
+ describe "with versions" do
207
+
208
+ before(:each) do
209
+ @resource = CloudKit::Resource.create(
210
+ CloudKit::URI.new('/items/123'),
211
+ JSON.generate({:foo => 'bar'}),
212
+ 'http://eric.dolphy.info')
213
+ @resource_list = [@resource.dup]
214
+
215
+ 2.times { |i|
216
+ now = Time.now
217
+ Time.stub!(:now).and_return(now+1)
218
+ @resource.update(JSON.generate({:foo => i}))
219
+ @resource_list << @resource.dup
220
+ }
221
+ @resource_list.reverse!
222
+ end
223
+
224
+ it "should keep an ordered list of versions" do
225
+ @resource.versions.map { |version| version.last_modified }.
226
+ should == @resource_list.map { |version| version.last_modified }
227
+ end
228
+
229
+ it "should include the current version in the version list" do
230
+ current_ts = @resource.last_modified
231
+ @resource_list.map { |version| version.last_modified }.should include(current_ts)
232
+ end
233
+
234
+ it "should know its previous version" do
235
+ @resource.previous_version.last_modified.should == @resource_list[1].last_modified
236
+ end
237
+
238
+ it "should know its previous versions" do
239
+ expected_times = @resource_list[1..-1].map { |version| version.last_modified }
240
+ @resource.previous_versions.map { |version| version.last_modified }.should == expected_times
241
+ end
242
+
243
+ end
244
+
245
+ describe "when finding" do
246
+
247
+ before(:each) do
248
+ ['bar', 'baz'].each do |value|
249
+ CloudKit::Resource.create(
250
+ CloudKit::URI.new('/items'),
251
+ JSON.generate({:foo => value}),
252
+ "http://eric.dolphy.info/#{value}_user")
253
+ end
254
+ CloudKit::Resource.create(
255
+ CloudKit::URI.new('/items'),
256
+ JSON.generate({:foo => 'box'}),
257
+ "http://eric.dolphy.info/bar_user")
258
+ end
259
+
260
+ describe "using #all" do
261
+
262
+ it "should find matching resources" do
263
+ result = CloudKit::Resource.all(
264
+ :remote_user => 'http://eric.dolphy.info/bar_user')
265
+ result.size.should == 2
266
+ result.map { |item| item.remote_user.should == 'http://eric.dolphy.info/bar_user' }
267
+ end
268
+
269
+ it "should return all elements if no restrictions are given" do
270
+ CloudKit::Resource.all.size.should == 3
271
+ end
272
+
273
+ it "should return an empty array if no resources are found" do
274
+ CloudKit::Resource.all(:uri => 'fail').should be_empty
275
+ end
276
+
277
+ it "should find with query parameters referencing JSON elements" do
278
+ resources = CloudKit::Resource.all(
279
+ :collection_reference => '/items',
280
+ :foo => 'bar')
281
+ resources.size.should == 1
282
+ resources.first.json.should == "{\"foo\":\"bar\"}"
283
+ end
284
+
285
+ end
286
+
287
+ describe "on #first" do
288
+
289
+ it "should find the first matching resource" do
290
+ result = CloudKit::Resource.first(:remote_user => 'http://eric.dolphy.info/bar_user')
291
+ result.should_not === Array
292
+ result.remote_user.should == 'http://eric.dolphy.info/bar_user'
293
+ result.parsed_json['foo'].should == 'box' # all listings are reverse ordered
294
+ end
295
+
296
+ end
297
+
298
+ describe "on #current" do
299
+
300
+ it "should find only current matching resources" do
301
+ resource = CloudKit::Resource.first(:remote_user => 'http://eric.dolphy.info/bar_user')
302
+ resource.update(JSON.generate({:foo => 'x'}))
303
+ CloudKit::Resource.current(:collection_reference => '/items').size.should == 3
304
+ end
305
+
306
+ end
307
+
308
+ end
309
+
310
+ end