riak-client 0.7.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/Rakefile +74 -0
- data/lib/riak.rb +49 -0
- data/lib/riak/bucket.rb +176 -0
- data/lib/riak/cache_store.rb +82 -0
- data/lib/riak/client.rb +139 -0
- data/lib/riak/client/curb_backend.rb +82 -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 +20 -0
- data/lib/riak/invalid_response.rb +25 -0
- data/lib/riak/link.rb +73 -0
- data/lib/riak/locale/en.yml +37 -0
- data/lib/riak/map_reduce.rb +248 -0
- data/lib/riak/map_reduce_error.rb +20 -0
- data/lib/riak/robject.rb +267 -0
- data/lib/riak/util/escape.rb +12 -0
- data/lib/riak/util/fiber1.8.rb +48 -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 +117 -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/integration/riak/cache_store_spec.rb +129 -0
- data/spec/riak/bucket_spec.rb +247 -0
- data/spec/riak/client_spec.rb +174 -0
- data/spec/riak/curb_backend_spec.rb +53 -0
- data/spec/riak/escape_spec.rb +21 -0
- data/spec/riak/headers_spec.rb +34 -0
- data/spec/riak/http_backend_spec.rb +131 -0
- data/spec/riak/link_spec.rb +82 -0
- data/spec/riak/map_reduce_spec.rb +352 -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 +538 -0
- data/spec/riak/walk_spec_spec.rb +208 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/http_backend_implementation_examples.rb +215 -0
- data/spec/support/mock_server.rb +61 -0
- data/spec/support/mocks.rb +3 -0
- metadata +187 -0
@@ -0,0 +1,36 @@
|
|
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::Util::Multipart do
|
17
|
+
it "should extract the boundary string from a header value" do
|
18
|
+
Riak::Util::Multipart.extract_boundary("multipart/mixed; boundary=123446677890").should == "123446677890"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should parse an empty multipart body into empty arrays" do
|
22
|
+
data = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-blank.txt"))
|
23
|
+
Riak::Util::Multipart.parse(data, "73NmmA8dJxSB5nL2dVerpFIi8ze").should == [[]]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should parse multipart body into nested arrays with response-like results" do
|
27
|
+
data = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
|
28
|
+
results = Riak::Util::Multipart.parse(data, "5EiMOjuGavQ2IbXAqsJPLLfJNlA")
|
29
|
+
results.should be_kind_of(Array)
|
30
|
+
results.first.should be_kind_of(Array)
|
31
|
+
obj = results.first.first
|
32
|
+
obj.should be_kind_of(Hash)
|
33
|
+
obj.should have_key(:headers)
|
34
|
+
obj.should have_key(:body)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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::Client::NetHTTPBackend do
|
17
|
+
before :each do
|
18
|
+
@client = Riak::Client.new
|
19
|
+
@backend = Riak::Client::NetHTTPBackend.new(@client)
|
20
|
+
FakeWeb.allow_net_connect = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_http_mock(method, uri, options={})
|
24
|
+
FakeWeb.register_uri(method, uri, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
it_should_behave_like "HTTP backend"
|
28
|
+
end
|
@@ -0,0 +1,538 @@
|
|
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 set" do
|
34
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
35
|
+
@object.links.should == Set.new
|
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
|
+
|
43
|
+
it "should yield itself to a given block" do
|
44
|
+
Riak::RObject.new(@bucket, "bar") do |r|
|
45
|
+
r.key.should == "bar"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "serialization" do
|
51
|
+
before :each do
|
52
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should change the data into a string by default when serializing" do
|
56
|
+
@object.serialize("foo").should == "foo"
|
57
|
+
@object.serialize(2).should == "2"
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not change the data when it is an IO" do
|
61
|
+
file = File.open("#{File.dirname(__FILE__)}/../fixtures/cat.jpg", "r")
|
62
|
+
file.should_not_receive(:to_s)
|
63
|
+
@object.serialize(file).should == file
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not modify the data by default when deserializing" do
|
67
|
+
@object.deserialize("foo").should == "foo"
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when the content type is YAML" do
|
71
|
+
before :each do
|
72
|
+
@object.content_type = "text/x-yaml"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should serialize into a YAML stream" do
|
76
|
+
@object.serialize({"foo" => "bar"}).should == "--- \nfoo: bar\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should deserialize a YAML stream" do
|
80
|
+
@object.deserialize("--- \nfoo: bar\n").should == {"foo" => "bar"}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "when the content type is JSON" do
|
85
|
+
before :each do
|
86
|
+
@object.content_type = "application/json"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should serialize into a JSON blob" do
|
90
|
+
@object.serialize({"foo" => "bar"}).should == '{"foo":"bar"}'
|
91
|
+
@object.serialize(2).should == "2"
|
92
|
+
@object.serialize("Some text").should == '"Some text"'
|
93
|
+
@object.serialize([1,2,3]).should == "[1,2,3]"
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should deserialize a JSON blob" do
|
97
|
+
@object.deserialize('{"foo":"bar"}').should == {"foo" => "bar"}
|
98
|
+
@object.deserialize("2").should == 2
|
99
|
+
@object.deserialize('"Some text"').should == "Some text"
|
100
|
+
@object.deserialize('[1,2,3]').should == [1,2,3]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "when the content type is an octet-stream" do
|
105
|
+
before :each do
|
106
|
+
@object.content_type = "application/octet-stream"
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "if the ruby-serialization meta field is set to Marshal" do
|
110
|
+
before :each do
|
111
|
+
@object.meta['ruby-serialization'] = "Marshal"
|
112
|
+
@payload = Marshal.dump({"foo" => "bar"})
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should dump via Marshal" do
|
116
|
+
@object.serialize({"foo" => "bar"}).should == @payload
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should load from Marshal" do
|
120
|
+
@object.deserialize(@payload).should == {"foo" => "bar"}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "if the ruby-serialization meta field is not set to Marshal" do
|
125
|
+
before :each do
|
126
|
+
@object.meta.delete("ruby-serialization")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should dump to a string" do
|
130
|
+
@object.serialize(2).should == "2"
|
131
|
+
@object.serialize("foo").should == "foo"
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should load the body unmodified" do
|
135
|
+
@object.deserialize("foo").should == "foo"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "loading data from the response" do
|
142
|
+
before :each do
|
143
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should load the content type" do
|
147
|
+
@object.load({:headers => {"content-type" => ["application/json"]}})
|
148
|
+
@object.content_type.should == "application/json"
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should load the body data" do
|
152
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
|
153
|
+
@object.data.should be_present
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should deserialize the body data" do
|
157
|
+
@object.should_receive(:deserialize).with("{}").and_return({})
|
158
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
|
159
|
+
@object.data.should == {}
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should leave the object data unchanged if the response body is blank" do
|
163
|
+
@object.data = "Original data"
|
164
|
+
@object.load({:headers => {"content-type" => ["application/json"]}, :body => ""})
|
165
|
+
@object.data.should == "Original data"
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should load the vclock from the headers" do
|
169
|
+
@object.load({:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
|
170
|
+
@object.vclock.should == "somereallylongbase64string=="
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should load links from the headers" do
|
174
|
+
@object.load({:headers => {"content-type" => ["application/json"], "link" => ['</riak/bar>; rel="up"']}, :body => "{}"})
|
175
|
+
@object.links.should have(1).item
|
176
|
+
@object.links.first.url.should == "/riak/bar"
|
177
|
+
@object.links.first.rel.should == "up"
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should load the ETag from the headers" do
|
181
|
+
@object.load({:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
|
182
|
+
@object.etag.should == "32748nvas83572934"
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should load the modified date from the headers" do
|
186
|
+
time = Time.now
|
187
|
+
@object.load({:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
|
188
|
+
@object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should load meta information from the headers" do
|
192
|
+
@object.load({:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
|
193
|
+
@object.meta["some-kind-of-robot"].should == ["for AWESOME"]
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should parse the location header into the key when present" do
|
197
|
+
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
|
198
|
+
@object.key.should == "baz"
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should be in conflict when the response code is 300 and the content-type is multipart/mixed" do
|
202
|
+
@object.load({:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
|
203
|
+
@object.should be_conflict
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should unescape the key given in the location header" do
|
207
|
+
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
|
208
|
+
@object.key.should == "baz "
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "extracting siblings" do
|
213
|
+
before :each do
|
214
|
+
@object = Riak::RObject.new(@bucket, "bar").load({:headers => {"x-riak-vclock" => ["merged"], "content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300, :body => "\n--foo\nContent-Type: text/plain\n\nbar\n--foo\nContent-Type: text/plain\n\nbaz\n--foo--\n"})
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should extract the siblings" do
|
218
|
+
@object.should have(2).siblings
|
219
|
+
siblings = @object.siblings
|
220
|
+
siblings[0].data.should == "bar"
|
221
|
+
siblings[1].data.should == "baz"
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should set the key on both siblings" do
|
225
|
+
@object.siblings.should be_all {|s| s.key == "bar" }
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should set the vclock on both siblings to the merged vclock" do
|
229
|
+
@object.siblings.should be_all {|s| s.vclock == "merged" }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe "headers used for storing the object" do
|
234
|
+
before :each do
|
235
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should include the content type" do
|
239
|
+
@object.content_type = "application/json"
|
240
|
+
@object.store_headers["Content-Type"].should == "application/json"
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should include the vclock when present" do
|
244
|
+
@object.vclock = "123445678990"
|
245
|
+
@object.store_headers["X-Riak-Vclock"].should == "123445678990"
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should exclude the vclock when nil" do
|
249
|
+
@object.vclock = nil
|
250
|
+
@object.store_headers.should_not have_key("X-Riak-Vclock")
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "when links are defined" do
|
254
|
+
before :each do
|
255
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should include a Link header with references to other objects" do
|
259
|
+
@object.store_headers.should have_key("Link")
|
260
|
+
@object.store_headers["Link"].should include('</riak/foo/baz>; riaktag="next"')
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should exclude the 'up' link to the bucket from the header" do
|
264
|
+
@object.links << Riak::Link.new("/riak/foo", "up")
|
265
|
+
@object.store_headers.should have_key("Link")
|
266
|
+
@object.store_headers["Link"].should_not include('riaktag="up"')
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should not allow duplicate links" do
|
270
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
271
|
+
@object.links.length.should == 1
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should exclude the Link header when no links are present" do
|
276
|
+
@object.links = Set.new
|
277
|
+
@object.store_headers.should_not have_key("Link")
|
278
|
+
end
|
279
|
+
|
280
|
+
describe "when meta fields are present" do
|
281
|
+
before :each do
|
282
|
+
@object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
|
283
|
+
end
|
284
|
+
|
285
|
+
it "should include X-Riak-Meta-* headers for each meta key" do
|
286
|
+
@object.store_headers.should have_key("X-Riak-Meta-some-kind-of-robot")
|
287
|
+
@object.store_headers.should have_key("X-Riak-Meta-cold-ones")
|
288
|
+
@object.store_headers.should have_key("X-Riak-Meta-powers")
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should turn non-string meta values into strings" do
|
292
|
+
@object.store_headers["X-Riak-Meta-some-kind-of-robot"].should == "true"
|
293
|
+
@object.store_headers["X-Riak-Meta-cold-ones"].should == "10"
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should leave string meta values unchanged in the header" do
|
297
|
+
@object.store_headers["X-Riak-Meta-powers"].should == "for awesome"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "headers used for reloading the object" do
|
303
|
+
before :each do
|
304
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should be blank when the etag and last_modified properties are blank" do
|
308
|
+
@object.etag.should be_blank
|
309
|
+
@object.last_modified.should be_blank
|
310
|
+
@object.reload_headers.should be_blank
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should include the If-None-Match key when the etag is present" do
|
314
|
+
@object.etag = "etag!"
|
315
|
+
@object.reload_headers['If-None-Match'].should == "etag!"
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should include the If-Modified-Since header when the last_modified time is present" do
|
319
|
+
time = Time.now
|
320
|
+
@object.last_modified = time
|
321
|
+
@object.reload_headers['If-Modified-Since'].should == time.httpdate
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "when storing the object normally" do
|
326
|
+
before :each do
|
327
|
+
@http = mock("HTTPBackend")
|
328
|
+
@client.stub!(:http).and_return(@http)
|
329
|
+
@object = Riak::RObject.new(@bucket)
|
330
|
+
@object.content_type = "text/plain"
|
331
|
+
@object.data = "This is some text."
|
332
|
+
@headers = @object.store_headers
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should raise an error when the content_type is blank" do
|
336
|
+
lambda { @object.content_type = nil; @object.store }.should raise_error(ArgumentError)
|
337
|
+
lambda { @object.content_type = " "; @object.store }.should raise_error(ArgumentError)
|
338
|
+
end
|
339
|
+
|
340
|
+
describe "when the object has no key" do
|
341
|
+
it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
|
342
|
+
@http.should_receive(:post).with(201, "/riak/", "foo", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
|
343
|
+
@object.store
|
344
|
+
@object.key.should == "somereallylongstring"
|
345
|
+
@object.vclock.should == "areallylonghashvalue"
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should include persistence-tuning parameters in the query string" do
|
349
|
+
@http.should_receive(:post).with(201, "/riak/", "foo", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
|
350
|
+
@object.store(:dw => 2)
|
351
|
+
end
|
352
|
+
|
353
|
+
it "should escape the bucket name" do
|
354
|
+
@bucket.should_receive(:name).and_return("foo ")
|
355
|
+
@http.should_receive(:post).with(201, "/riak/", "foo%20", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
|
356
|
+
@object.store
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
describe "when the object has a key" do
|
361
|
+
before :each do
|
362
|
+
@object.key = "bar"
|
363
|
+
end
|
364
|
+
|
365
|
+
it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
|
366
|
+
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
367
|
+
@object.store
|
368
|
+
@object.key.should == "somereallylongstring"
|
369
|
+
@object.vclock.should == "areallylonghashvalue"
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should include persistence-tuning parameters in the query string" do
|
373
|
+
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
374
|
+
@object.store(:dw => 2)
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should escape the bucket and key names" do
|
378
|
+
@http.should_receive(:put).with([200,204,300], "/riak/", "foo%20/bar%2Fbaz", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
379
|
+
@bucket.instance_variable_set(:@name, "foo ")
|
380
|
+
@object.key = "bar/baz"
|
381
|
+
@object.store
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe "when reloading the object" do
|
387
|
+
before :each do
|
388
|
+
@http = mock("HTTPBackend")
|
389
|
+
@client.stub!(:http).and_return(@http)
|
390
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
391
|
+
@object.vclock = "somereallylongstring"
|
392
|
+
@object.stub!(:reload_headers).and_return({})
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should return without requesting if the key is blank" do
|
396
|
+
@object.key = nil
|
397
|
+
@http.should_not_receive(:get)
|
398
|
+
@object.reload
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should return without requesting if the vclock is blank" do
|
402
|
+
@object.vclock = nil
|
403
|
+
@http.should_not_receive(:get)
|
404
|
+
@object.reload
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should make the request if the key is present and the :force option is given" do
|
408
|
+
@http.should_receive(:get).and_return({:headers => {}, :code => 304})
|
409
|
+
@object.reload :force => true
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should pass along the reload_headers" do
|
413
|
+
@headers = {"If-None-Match" => "etag"}
|
414
|
+
@object.should_receive(:reload_headers).and_return(@headers)
|
415
|
+
@http.should_receive(:get).with([200,304], "/riak/", "foo", "bar", {}, @headers).and_return({:code => 304})
|
416
|
+
@object.reload
|
417
|
+
end
|
418
|
+
|
419
|
+
it "should return without modifying the object if the response is 304 Not Modified" do
|
420
|
+
@http.should_receive(:get).and_return({:code => 304})
|
421
|
+
@object.should_not_receive(:load)
|
422
|
+
@object.reload
|
423
|
+
end
|
424
|
+
|
425
|
+
it "should raise an exception when the response code is not 200 or 304" do
|
426
|
+
@http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
|
427
|
+
@object.should_not_receive(:load)
|
428
|
+
lambda { @object.reload }.should raise_error(Riak::FailedRequest)
|
429
|
+
end
|
430
|
+
|
431
|
+
it "should include 300 in valid responses if the bucket has allow_mult set" do
|
432
|
+
@object.bucket.should_receive(:allow_mult).and_return(true)
|
433
|
+
@http.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {}).and_return({:code => 304})
|
434
|
+
@object.reload
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should escape the bucket and key names" do
|
438
|
+
@bucket.should_receive(:name).and_return("some/deep/path")
|
439
|
+
@object.key = "another/deep/path"
|
440
|
+
@http.should_receive(:get).with([200,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
|
441
|
+
@object.reload
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe "walking from the object to linked objects" do
|
446
|
+
before :each do
|
447
|
+
@http = mock("HTTPBackend")
|
448
|
+
@client.stub!(:http).and_return(@http)
|
449
|
+
@client.stub!(:bucket).and_return(@bucket)
|
450
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
451
|
+
@body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should issue a GET request to the given walk spec" do
|
455
|
+
@http.should_receive(:get).with(200, "/riak/", "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")
|
456
|
+
@object.walk(nil,"next",true)
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should parse the results into arrays of objects" do
|
460
|
+
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
461
|
+
results = @object.walk(nil,"next",true)
|
462
|
+
results.should be_kind_of(Array)
|
463
|
+
results.first.should be_kind_of(Array)
|
464
|
+
obj = results.first.first
|
465
|
+
obj.should be_kind_of(Riak::RObject)
|
466
|
+
obj.content_type.should == "text/plain"
|
467
|
+
obj.key.should == "baz"
|
468
|
+
obj.bucket.should == @bucket
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should assign the bucket for newly parsed objects" do
|
472
|
+
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
473
|
+
@client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
|
474
|
+
@object.walk(nil,"next",true)
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should escape the bucket, key and link specs" do
|
478
|
+
@object.key = "bar/baz"
|
479
|
+
@bucket.should_receive(:name).and_return("quin/quux")
|
480
|
+
@http.should_receive(:get).with(200, "/riak/", "quin%2Fquux", "bar%2Fbaz", "_,next%2F2,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")
|
481
|
+
@object.walk(:tag => "next/2", :keep => true)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
describe "when deleting" do
|
486
|
+
before :each do
|
487
|
+
@http = mock("HTTPBackend")
|
488
|
+
@client.stub!(:http).and_return(@http)
|
489
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
490
|
+
end
|
491
|
+
|
492
|
+
it "should make a DELETE request to the Riak server and freeze the object" do
|
493
|
+
@http.should_receive(:delete).with([204,404], "/riak/", "foo", "bar", {},{}).and_return({:code => 204, :headers => {}})
|
494
|
+
@object.delete
|
495
|
+
@object.should be_frozen
|
496
|
+
end
|
497
|
+
|
498
|
+
it "should do nothing when the key is blank" do
|
499
|
+
@http.should_not_receive(:delete)
|
500
|
+
@object.key = nil
|
501
|
+
@object.delete
|
502
|
+
end
|
503
|
+
|
504
|
+
it "should pass through a failed request exception" do
|
505
|
+
@http.should_receive(:delete).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
|
506
|
+
lambda { @object.delete }.should raise_error(Riak::FailedRequest)
|
507
|
+
end
|
508
|
+
|
509
|
+
it "should escape the bucket and key names" do
|
510
|
+
@object.key = "deep/path"
|
511
|
+
@bucket.should_receive(:name).and_return("bucket spaces")
|
512
|
+
@http.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
|
513
|
+
@object.delete
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
it "should convert to a link having the same url and an empty tag" do
|
518
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
519
|
+
@object.to_link.should == Riak::Link.new("/riak/foo/bar", nil)
|
520
|
+
end
|
521
|
+
|
522
|
+
it "should convert to a link having the same url and a supplied tag" do
|
523
|
+
@object = Riak::RObject.new(@bucket, "bar")
|
524
|
+
@object.to_link("next").should == Riak::Link.new("/riak/foo/bar", "next")
|
525
|
+
end
|
526
|
+
|
527
|
+
it "should escape the bucket and key when converting to a link" do
|
528
|
+
@object = Riak::RObject.new(@bucket, "deep/path")
|
529
|
+
@bucket.should_receive(:name).and_return("bucket spaces")
|
530
|
+
@object.to_link("bar").url.should == "/riak/bucket%20spaces/deep%2Fpath"
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should provide a useful inspect output even when the key is nil" do
|
534
|
+
@object = Riak::RObject.new(@bucket)
|
535
|
+
lambda { @object.inspect }.should_not raise_error
|
536
|
+
@object.inspect.should be_kind_of(String)
|
537
|
+
end
|
538
|
+
end
|