ripple 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +26 -0
- data/LICENSE +13 -0
- data/README.textile +126 -0
- data/RELEASE_NOTES.textile +24 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/lib/riak.rb +45 -0
- data/lib/riak/bucket.rb +105 -0
- data/lib/riak/client.rb +138 -0
- data/lib/riak/client/curb_backend.rb +63 -0
- data/lib/riak/client/http_backend.rb +209 -0
- data/lib/riak/client/net_http_backend.rb +49 -0
- data/lib/riak/failed_request.rb +37 -0
- data/lib/riak/i18n.rb +15 -0
- data/lib/riak/invalid_response.rb +25 -0
- data/lib/riak/link.rb +54 -0
- data/lib/riak/locale/en.yml +37 -0
- data/lib/riak/map_reduce.rb +240 -0
- data/lib/riak/map_reduce_error.rb +20 -0
- data/lib/riak/robject.rb +234 -0
- data/lib/riak/util/headers.rb +44 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/translation.rb +29 -0
- data/lib/riak/walk_spec.rb +113 -0
- data/lib/ripple.rb +48 -0
- data/lib/ripple/core_ext/casting.rb +96 -0
- data/lib/ripple/document.rb +60 -0
- data/lib/ripple/document/attribute_methods.rb +111 -0
- data/lib/ripple/document/attribute_methods/dirty.rb +52 -0
- data/lib/ripple/document/attribute_methods/query.rb +49 -0
- data/lib/ripple/document/attribute_methods/read.rb +38 -0
- data/lib/ripple/document/attribute_methods/write.rb +36 -0
- data/lib/ripple/document/bucket_access.rb +38 -0
- data/lib/ripple/document/finders.rb +84 -0
- data/lib/ripple/document/persistence.rb +93 -0
- data/lib/ripple/document/persistence/callbacks.rb +48 -0
- data/lib/ripple/document/properties.rb +85 -0
- data/lib/ripple/document/validations.rb +44 -0
- data/lib/ripple/embedded_document.rb +38 -0
- data/lib/ripple/embedded_document/persistence.rb +46 -0
- data/lib/ripple/i18n.rb +15 -0
- data/lib/ripple/locale/en.yml +16 -0
- data/lib/ripple/property_type_mismatch.rb +23 -0
- data/lib/ripple/translation.rb +24 -0
- data/ripple.gemspec +159 -0
- data/spec/fixtures/cat.jpg +0 -0
- data/spec/fixtures/multipart-blank.txt +7 -0
- data/spec/fixtures/multipart-with-body.txt +16 -0
- data/spec/riak/bucket_spec.rb +141 -0
- data/spec/riak/client_spec.rb +169 -0
- data/spec/riak/curb_backend_spec.rb +50 -0
- data/spec/riak/headers_spec.rb +34 -0
- data/spec/riak/http_backend_spec.rb +136 -0
- data/spec/riak/link_spec.rb +50 -0
- data/spec/riak/map_reduce_spec.rb +347 -0
- data/spec/riak/multipart_spec.rb +36 -0
- data/spec/riak/net_http_backend_spec.rb +28 -0
- data/spec/riak/object_spec.rb +444 -0
- data/spec/riak/walk_spec_spec.rb +208 -0
- data/spec/ripple/attribute_methods_spec.rb +149 -0
- data/spec/ripple/bucket_access_spec.rb +48 -0
- data/spec/ripple/callbacks_spec.rb +86 -0
- data/spec/ripple/document_spec.rb +35 -0
- data/spec/ripple/embedded_document_spec.rb +52 -0
- data/spec/ripple/finders_spec.rb +146 -0
- data/spec/ripple/persistence_spec.rb +89 -0
- data/spec/ripple/properties_spec.rb +195 -0
- data/spec/ripple/ripple_spec.rb +43 -0
- data/spec/ripple/validations_spec.rb +64 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/http_backend_implementation_examples.rb +215 -0
- data/spec/support/mock_server.rb +58 -0
- metadata +221 -0
@@ -0,0 +1,444 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
15
|
+
|
16
|
+
describe Riak::RObject do
|
17
|
+
before :each do
|
18
|
+
@client = Riak::Client.new
|
19
|
+
@bucket = Riak::Bucket.new(@client, "foo")
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "initialization" do
|
23
|
+
it "should set the bucket" do
|
24
|
+
@object = Riak::RObject.new(@bucket)
|
25
|
+
@object.bucket.should == @bucket
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should set the key" do
|
29
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
30
|
+
@object.key.should == "bar"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should initialize the links to an empty array" do
|
34
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
35
|
+
@object.links.should == []
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should initialize the meta to an empty hash" do
|
39
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
40
|
+
@object.meta.should == {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "serialization" do
|
45
|
+
before :each do
|
46
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should change the data into a string by default when serializing" do
|
50
|
+
@object.serialize("foo").should == "foo"
|
51
|
+
@object.serialize(2).should == "2"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not change the data when it is an IO" do
|
55
|
+
file = File.open("#{File.dirname(__FILE__)}/../fixtures/cat.jpg", "r")
|
56
|
+
file.should_not_receive(:to_s)
|
57
|
+
@object.serialize(file).should == file
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not modify the data by default when deserializing" do
|
61
|
+
@object.deserialize("foo").should == "foo"
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "when the content type is YAML" do
|
65
|
+
before :each do
|
66
|
+
@object.content_type = "text/x-yaml"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should serialize into a YAML stream" do
|
70
|
+
@object.serialize({"foo" => "bar"}).should == "--- \nfoo: bar\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should deserialize a YAML stream" do
|
74
|
+
@object.deserialize("--- \nfoo: bar\n").should == {"foo" => "bar"}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when the content type is JSON" do
|
79
|
+
before :each do
|
80
|
+
@object.content_type = "application/json"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should serialize into a JSON blob" do
|
84
|
+
@object.serialize({"foo" => "bar"}).should == '{"foo":"bar"}'
|
85
|
+
@object.serialize(2).should == "2"
|
86
|
+
@object.serialize("Some text").should == '"Some text"'
|
87
|
+
@object.serialize([1,2,3]).should == "[1,2,3]"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should deserialize a JSON blob" do
|
91
|
+
@object.deserialize('{"foo":"bar"}').should == {"foo" => "bar"}
|
92
|
+
@object.deserialize("2").should == 2
|
93
|
+
@object.deserialize('"Some text"').should == "Some text"
|
94
|
+
@object.deserialize('[1,2,3]').should == [1,2,3]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "when the content type is an octet-stream" do
|
99
|
+
before :each do
|
100
|
+
@object.content_type = "application/octet-stream"
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "if the ruby-serialization meta field is set to Marshal" do
|
104
|
+
before :each do
|
105
|
+
@object.meta['ruby-serialization'] = "Marshal"
|
106
|
+
@payload = Marshal.dump({"foo" => "bar"})
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should dump via Marshal" do
|
110
|
+
@object.serialize({"foo" => "bar"}).should == @payload
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should load from Marshal" do
|
114
|
+
@object.deserialize(@payload).should == {"foo" => "bar"}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "if the ruby-serialization meta field is not set to Marshal" do
|
119
|
+
before :each do
|
120
|
+
@object.meta.delete("ruby-serialization")
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should dump to a string" do
|
124
|
+
@object.serialize(2).should == "2"
|
125
|
+
@object.serialize("foo").should == "foo"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should load the body unmodified" do
|
129
|
+
@object.deserialize("foo").should == "foo"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "loading data from the response" do
|
136
|
+
before :each do
|
137
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should load the content type" do
|
141
|
+
@object.load({:headers => {"content-type" => ["application/json"]}})
|
142
|
+
@object.content_type.should == "application/json"
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should load the body data" do
|
146
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
|
147
|
+
@object.data.should be_present
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should deserialize the body data" do
|
151
|
+
@object.should_receive(:deserialize).with("{}").and_return({})
|
152
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
|
153
|
+
@object.data.should == {}
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should leave the object data unchanged if the response body is blank" do
|
157
|
+
@object.data = "Original data"
|
158
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => ""})
|
159
|
+
@object.data.should == "Original data"
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should load the vclock from the headers" do
|
163
|
+
@object.load({:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
|
164
|
+
@object.vclock.should == "somereallylongbase64string=="
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should load links from the headers" do
|
168
|
+
@object.load({:headers => {"content-type" => ["application/json"], "link" => ['</raw/bar>; rel="up"']}, :body => "{}"})
|
169
|
+
@object.links.should have(1).item
|
170
|
+
@object.links.first.url.should == "/raw/bar"
|
171
|
+
@object.links.first.rel.should == "up"
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should load the ETag from the headers" do
|
175
|
+
@object.load({:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
|
176
|
+
@object.etag.should == "32748nvas83572934"
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should load the modified date from the headers" do
|
180
|
+
time = Time.now
|
181
|
+
@object.load({:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
|
182
|
+
@object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should load meta information from the headers" do
|
186
|
+
@object.load({:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
|
187
|
+
@object.meta["some-kind-of-robot"].should == ["for AWESOME"]
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should parse the location header into the key when present" do
|
191
|
+
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/raw/foo/baz"]}})
|
192
|
+
@object.key.should == "baz"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "headers used for storing the object" do
|
197
|
+
before :each do
|
198
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should include the content type" do
|
202
|
+
@object.content_type = "application/json"
|
203
|
+
@object.store_headers["Content-Type"].should == "application/json"
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should include the vclock when present" do
|
207
|
+
@object.vclock = "123445678990"
|
208
|
+
@object.store_headers["X-Riak-Vclock"].should == "123445678990"
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should exclude the vclock when nil" do
|
212
|
+
@object.vclock = nil
|
213
|
+
@object.store_headers.should_not have_key("X-Riak-Vclock")
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "when links are defined" do
|
217
|
+
before :each do
|
218
|
+
@object.links = [Riak::Link.new("/raw/foo/baz", "next")]
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should include a Link header with references to other objects" do
|
222
|
+
@object.store_headers.should have_key("Link")
|
223
|
+
@object.store_headers["Link"].should include('</raw/foo/baz>; riaktag="next"')
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should exclude the 'up' link to the bucket from the header" do
|
227
|
+
@object.links << Riak::Link.new("/raw/foo", "up")
|
228
|
+
@object.store_headers.should have_key("Link")
|
229
|
+
@object.store_headers["Link"].should_not include('riaktag="up"')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should exclude the Link header when no links are present" do
|
234
|
+
@object.links = []
|
235
|
+
@object.store_headers.should_not have_key("Link")
|
236
|
+
end
|
237
|
+
|
238
|
+
describe "when meta fields are present" do
|
239
|
+
before :each do
|
240
|
+
@object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should include X-Riak-Meta-* headers for each meta key" do
|
244
|
+
@object.store_headers.should have_key("X-Riak-Meta-some-kind-of-robot")
|
245
|
+
@object.store_headers.should have_key("X-Riak-Meta-cold-ones")
|
246
|
+
@object.store_headers.should have_key("X-Riak-Meta-powers")
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should turn non-string meta values into strings" do
|
250
|
+
@object.store_headers["X-Riak-Meta-some-kind-of-robot"].should == "true"
|
251
|
+
@object.store_headers["X-Riak-Meta-cold-ones"].should == "10"
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should leave string meta values unchanged in the header" do
|
255
|
+
@object.store_headers["X-Riak-Meta-powers"].should == "for awesome"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe "headers used for reloading the object" do
|
261
|
+
before :each do
|
262
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should be blank when the etag and last_modified properties are blank" do
|
266
|
+
@object.etag.should be_blank
|
267
|
+
@object.last_modified.should be_blank
|
268
|
+
@object.reload_headers.should be_blank
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should include the If-None-Match key when the etag is present" do
|
272
|
+
@object.etag = "etag!"
|
273
|
+
@object.reload_headers['If-None-Match'].should == "etag!"
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should include the If-Modified-Since header when the last_modified time is present" do
|
277
|
+
time = Time.now
|
278
|
+
@object.last_modified = time
|
279
|
+
@object.reload_headers['If-Modified-Since'].should == time.httpdate
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "when storing the object normally" do
|
284
|
+
before :each do
|
285
|
+
@http = mock("HTTPBackend")
|
286
|
+
@client.stub!(:http).and_return(@http)
|
287
|
+
@object = Riak::RObject.new(@bucket)
|
288
|
+
@object.content_type = "text/plain"
|
289
|
+
@object.data = "This is some text."
|
290
|
+
@headers = @object.store_headers
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should raise an error when the content_type is blank" do
|
294
|
+
lambda { @object.content_type = nil; @object.store }.should raise_error(ArgumentError)
|
295
|
+
lambda { @object.content_type = " "; @object.store }.should raise_error(ArgumentError)
|
296
|
+
end
|
297
|
+
|
298
|
+
describe "when the object has no key" do
|
299
|
+
it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
|
300
|
+
@http.should_receive(:post).with(201, "/raw/", "foo", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/raw/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
|
301
|
+
@object.store
|
302
|
+
@object.key.should == "somereallylongstring"
|
303
|
+
@object.vclock.should == "areallylonghashvalue"
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should include persistence-tuning parameters in the query string" do
|
307
|
+
@http.should_receive(:post).with(201, "/raw/", "foo", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/raw/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
|
308
|
+
@object.store(:dw => 2)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe "when the object has a key" do
|
313
|
+
before :each do
|
314
|
+
@object.key = "bar"
|
315
|
+
end
|
316
|
+
|
317
|
+
it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
|
318
|
+
@http.should_receive(:put).with([200,204], "/raw/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/raw/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
319
|
+
@object.store
|
320
|
+
@object.key.should == "somereallylongstring"
|
321
|
+
@object.vclock.should == "areallylonghashvalue"
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should include persistence-tuning parameters in the query string" do
|
325
|
+
@http.should_receive(:put).with([200,204], "/raw/", "foo/bar", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/raw/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
326
|
+
@object.store(:dw => 2)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
describe "when reloading the object" do
|
332
|
+
before :each do
|
333
|
+
@http = mock("HTTPBackend")
|
334
|
+
@client.stub!(:http).and_return(@http)
|
335
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
336
|
+
@object.vclock = "somereallylongstring"
|
337
|
+
@object.stub!(:reload_headers).and_return({})
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should return without requesting if the key is blank" do
|
341
|
+
@object.key = nil
|
342
|
+
@http.should_not_receive(:get)
|
343
|
+
@object.reload
|
344
|
+
end
|
345
|
+
|
346
|
+
it "should return without requesting if the vclock is blank" do
|
347
|
+
@object.vclock = nil
|
348
|
+
@http.should_not_receive(:get)
|
349
|
+
@object.reload
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should make the request if the key is present and the :force option is given" do
|
353
|
+
@http.should_receive(:get).and_return({:headers => {}, :code => 304})
|
354
|
+
@object.reload :force => true
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should pass along the reload_headers" do
|
358
|
+
@headers = {"If-None-Match" => "etag"}
|
359
|
+
@object.should_receive(:reload_headers).and_return(@headers)
|
360
|
+
@http.should_receive(:get).with([200,304], "/raw/", "foo", "bar", {}, @headers).and_return({:code => 304})
|
361
|
+
@object.reload
|
362
|
+
end
|
363
|
+
|
364
|
+
it "should return without modifying the object if the response is 304 Not Modified" do
|
365
|
+
@http.should_receive(:get).and_return({:code => 304})
|
366
|
+
@object.should_not_receive(:load)
|
367
|
+
@object.reload
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should raise an exception when the response code is not 200 or 304" do
|
371
|
+
@http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
|
372
|
+
@object.should_not_receive(:load)
|
373
|
+
lambda { @object.reload }.should raise_error(Riak::FailedRequest)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe "walking from the object to linked objects" do
|
378
|
+
before :each do
|
379
|
+
@http = mock("HTTPBackend")
|
380
|
+
@client.stub!(:http).and_return(@http)
|
381
|
+
@client.stub!(:bucket).and_return(@bucket)
|
382
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
383
|
+
@body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should issue a GET request to the given walk spec" do
|
387
|
+
@http.should_receive(:get).with(200, "/raw/", "foo", "bar", "_,next,1").and_return(:headers => {"content-type" => ["multipart/mixed; boundary=12345"]}, :body => "\n--12345\nContent-Type: multipart/mixed; boundary=09876\n\n--09876--\n\n--12345--\n")
|
388
|
+
@object.walk(nil,"next",true)
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should parse the results into arrays of objects" do
|
392
|
+
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
393
|
+
results = @object.walk(nil,"next",true)
|
394
|
+
results.should be_kind_of(Array)
|
395
|
+
results.first.should be_kind_of(Array)
|
396
|
+
obj = results.first.first
|
397
|
+
obj.should be_kind_of(Riak::RObject)
|
398
|
+
obj.content_type.should == "text/plain"
|
399
|
+
obj.key.should == "baz"
|
400
|
+
obj.bucket.should == @bucket
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should assign the bucket for newly parsed objects" do
|
404
|
+
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
405
|
+
@client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
|
406
|
+
@object.walk(nil,"next",true)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe "when deleting" do
|
411
|
+
before :each do
|
412
|
+
@http = mock("HTTPBackend")
|
413
|
+
@client.stub!(:http).and_return(@http)
|
414
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should make a DELETE request to the Riak server and freeze the object" do
|
418
|
+
@http.should_receive(:delete).with([204,404], "/raw/", "foo", "bar").and_return({:code => 204, :headers => {}})
|
419
|
+
@object.delete
|
420
|
+
@object.should be_frozen
|
421
|
+
end
|
422
|
+
|
423
|
+
it "should do nothing when the key is blank" do
|
424
|
+
@http.should_not_receive(:delete)
|
425
|
+
@object.key = nil
|
426
|
+
@object.delete
|
427
|
+
end
|
428
|
+
|
429
|
+
it "should pass through a failed request exception" do
|
430
|
+
@http.should_receive(:delete).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
|
431
|
+
lambda { @object.delete }.should raise_error(Riak::FailedRequest)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
it "should convert to a link having the same url and an empty tag" do
|
436
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
437
|
+
@object.to_link.should == Riak::Link.new("/raw/foo/bar", nil)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should convert to a link having the same url and a supplied tag" do
|
441
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
442
|
+
@object.to_link("next").should == Riak::Link.new("/raw/foo/bar", "next")
|
443
|
+
end
|
444
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
15
|
+
|
16
|
+
describe Riak::WalkSpec do
|
17
|
+
describe "initializing" do
|
18
|
+
describe "with a hash" do
|
19
|
+
it "should be empty by default" do
|
20
|
+
spec = Riak::WalkSpec.new({})
|
21
|
+
spec.bucket.should == "_"
|
22
|
+
spec.tag.should == "_"
|
23
|
+
spec.keep.should be_false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should extract the bucket" do
|
27
|
+
spec = Riak::WalkSpec.new({:bucket => "foo"})
|
28
|
+
spec.bucket.should == "foo"
|
29
|
+
spec.tag.should == "_"
|
30
|
+
spec.keep.should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should extract the tag" do
|
34
|
+
spec = Riak::WalkSpec.new({:tag => "foo"})
|
35
|
+
spec.bucket.should == "_"
|
36
|
+
spec.tag.should == "foo"
|
37
|
+
spec.keep.should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should extract the keep" do
|
41
|
+
spec = Riak::WalkSpec.new({:keep => true})
|
42
|
+
spec.bucket.should == "_"
|
43
|
+
spec.tag.should == "_"
|
44
|
+
spec.keep.should be_true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with three arguments for bucket, tag, and keep" do
|
49
|
+
it "should assign the bucket, tag, and keep" do
|
50
|
+
spec = Riak::WalkSpec.new("foo", "next", false)
|
51
|
+
spec.bucket.should == "foo"
|
52
|
+
spec.tag.should == "next"
|
53
|
+
spec.keep.should be_false
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should make the bucket '_' when false or nil" do
|
57
|
+
spec = Riak::WalkSpec.new(nil, "next", false)
|
58
|
+
spec.bucket.should == "_"
|
59
|
+
spec = Riak::WalkSpec.new(false, "next", false)
|
60
|
+
spec.bucket.should == "_"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should make the tag '_' when false or nil" do
|
64
|
+
spec = Riak::WalkSpec.new("foo", nil, false)
|
65
|
+
spec.tag.should == "_"
|
66
|
+
spec = Riak::WalkSpec.new("foo", false, false)
|
67
|
+
spec.tag.should == "_"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should make the keep false when false or nil" do
|
71
|
+
spec = Riak::WalkSpec.new(nil, nil, nil)
|
72
|
+
spec.keep.should be_false
|
73
|
+
spec = Riak::WalkSpec.new(nil, nil, false)
|
74
|
+
spec.keep.should be_false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should raise an ArgumentError for invalid arguments" do
|
79
|
+
lambda { Riak::WalkSpec.new }.should raise_error(ArgumentError)
|
80
|
+
lambda { Riak::WalkSpec.new("foo") }.should raise_error(ArgumentError)
|
81
|
+
lambda { Riak::WalkSpec.new("foo","bar") }.should raise_error(ArgumentError)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "converting to a string" do
|
86
|
+
before :each do
|
87
|
+
@spec = Riak::WalkSpec.new({})
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should be the empty spec by default" do
|
91
|
+
@spec.to_s.should == "_,_,_"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should include the bucket when set" do
|
95
|
+
@spec.bucket = "foo"
|
96
|
+
@spec.to_s.should == "foo,_,_"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should include the tag when set" do
|
100
|
+
@spec.tag = "next"
|
101
|
+
@spec.to_s.should == "_,next,_"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should include the keep when true" do
|
105
|
+
@spec.keep = true
|
106
|
+
@spec.to_s.should == "_,_,1"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "creating from a list of parameters" do
|
111
|
+
it "should detect hashes and WalkSpecs interleaved with other parameters" do
|
112
|
+
specs = Riak::WalkSpec.normalize(nil,"next",nil,{:bucket => "foo"},Riak::WalkSpec.new({:tag => "child", :keep => true}))
|
113
|
+
specs.should have(3).items
|
114
|
+
specs.should be_all {|s| s.kind_of?(Riak::WalkSpec) }
|
115
|
+
specs.join("/").should == "_,next,_/foo,_,_/_,child,1"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "matching other objects with ===" do
|
120
|
+
before :each do
|
121
|
+
@spec = Riak::WalkSpec.new({})
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should not match objects that aren't links or walk specs" do
|
125
|
+
@spec.should_not === "foo"
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "matching links" do
|
129
|
+
before :each do
|
130
|
+
@link = Riak::Link.new("/raw/foo/bar", "next")
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should match a link when the bucket and tag are not specified" do
|
134
|
+
@spec.should === @link
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should match a link when the bucket is the same" do
|
138
|
+
@spec.bucket = "foo"
|
139
|
+
@spec.should === @link
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should not match a link when the bucket is different" do
|
143
|
+
@spec.bucket = "bar"
|
144
|
+
@spec.should_not === @link
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should match a link when the tag is the same" do
|
148
|
+
@spec.tag = "next"
|
149
|
+
@spec.should === @link
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not match a link when the tag is different" do
|
153
|
+
@spec.tag = "previous"
|
154
|
+
@spec.should_not === @link
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should match a link when the bucket and tag are the same" do
|
158
|
+
@spec.bucket = "foo"
|
159
|
+
@spec.should === @link
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "matching walk specs" do
|
164
|
+
before :each do
|
165
|
+
@other = Riak::WalkSpec.new({})
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should match a walk spec that is equivalent" do
|
169
|
+
@spec.should === @other
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should not match a walk spec that has a different keep value" do
|
173
|
+
@other.keep = true
|
174
|
+
@spec.should_not === @other
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should match a walk spec with a more specific bucket" do
|
178
|
+
@other.bucket = "foo"
|
179
|
+
@spec.should === @other
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should match a walk spec with the same bucket" do
|
183
|
+
@other.bucket = "foo"; @spec.bucket = "foo"
|
184
|
+
@spec.should === @other
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should not match a walk spec with a different bucket" do
|
188
|
+
@other.bucket = "foo"; @spec.bucket = "bar"
|
189
|
+
@spec.should_not === @other
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should match a walk spec with a more specific tag" do
|
193
|
+
@other.tag = "next"
|
194
|
+
@spec.should === @other
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should match a walk spec with the same tag" do
|
198
|
+
@other.tag = "next"; @spec.tag = "next"
|
199
|
+
@spec.should === @other
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should not match a walk spec with a different tag" do
|
203
|
+
@other.tag = "next"; @spec.tag = "previous"
|
204
|
+
@spec.should_not === @other
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|