cloudkit-jruby 0.11.2

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.
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