ripple 0.5.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/.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
|