riak-client 0.8.3 → 0.9.0.beta
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 +10 -3
- data/lib/riak/bucket.rb +31 -46
- data/lib/riak/client.rb +18 -4
- data/lib/riak/client/excon_backend.rb +15 -1
- data/lib/riak/client/http_backend.rb +169 -196
- data/lib/riak/client/http_backend/configuration.rb +56 -0
- data/lib/riak/client/http_backend/object_methods.rb +101 -0
- data/lib/riak/client/http_backend/request_headers.rb +46 -0
- data/lib/riak/client/http_backend/transport_methods.rb +208 -0
- data/lib/riak/core_ext/blank.rb +14 -2
- data/lib/riak/link.rb +9 -4
- data/lib/riak/locale/en.yml +2 -0
- data/lib/riak/map_reduce.rb +37 -103
- data/lib/riak/map_reduce/filter_builder.rb +106 -0
- data/lib/riak/map_reduce/phase.rb +108 -0
- data/lib/riak/robject.rb +19 -96
- data/lib/riak/util/escape.rb +7 -0
- data/riak-client.gemspec +5 -5
- data/spec/riak/bucket_spec.rb +57 -111
- data/spec/riak/client_spec.rb +97 -78
- data/spec/riak/escape_spec.rb +7 -0
- data/spec/riak/http_backend/object_methods_spec.rb +218 -0
- data/spec/riak/http_backend_spec.rb +204 -65
- data/spec/riak/link_spec.rb +8 -0
- data/spec/riak/map_reduce_spec.rb +26 -151
- data/spec/riak/object_spec.rb +36 -350
- data/spec/riak/search_spec.rb +5 -16
- data/spec/spec_helper.rb +1 -1
- metadata +29 -8
data/spec/riak/link_spec.rb
CHANGED
@@ -32,11 +32,19 @@ describe Riak::Link do
|
|
32
32
|
result[1].key.should == nil
|
33
33
|
result[1].rel.should == "up"
|
34
34
|
end
|
35
|
+
|
36
|
+
it "should keep the url intact if it does not point to a bucket or bucket/key" do
|
37
|
+
result = Riak::Link.parse('</mapred>; rel="riak_kv_wm_mapred"')
|
38
|
+
result[0].url.should == "/mapred"
|
39
|
+
result[0].bucket.should be_nil
|
40
|
+
result[0].key.should be_nil
|
41
|
+
end
|
35
42
|
end
|
36
43
|
|
37
44
|
it "should convert to a string appropriate for use in the Link header" do
|
38
45
|
Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
|
39
46
|
Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
|
47
|
+
Riak::Link.new("/riak", "riak_kv_wm_raw").to_s.should == '</riak>; riaktag="riak_kv_wm_raw"'
|
40
48
|
end
|
41
49
|
|
42
50
|
it "should convert to a walk spec when pointing to an object" do
|
@@ -16,8 +16,8 @@ require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
|
16
16
|
describe Riak::MapReduce do
|
17
17
|
before :each do
|
18
18
|
@client = Riak::Client.new
|
19
|
-
@
|
20
|
-
@client.stub!(:
|
19
|
+
@backend = mock("Backend")
|
20
|
+
@client.stub!(:backend).and_return(@backend)
|
21
21
|
@mr = Riak::MapReduce.new(@client)
|
22
22
|
end
|
23
23
|
|
@@ -101,6 +101,20 @@ describe Riak::MapReduce do
|
|
101
101
|
@mr.add("docs")
|
102
102
|
@mr.inputs.should == "docs"
|
103
103
|
end
|
104
|
+
|
105
|
+
it "should accept a list of key-filters along with a bucket" do
|
106
|
+
@mr.add("foo", [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]])
|
107
|
+
@mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should add a bucket and filter list via a builder block" do
|
111
|
+
@mr.filter("foo") do
|
112
|
+
tokenize "-", 3
|
113
|
+
string_to_int
|
114
|
+
between 2009, 2010
|
115
|
+
end
|
116
|
+
@mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
|
117
|
+
end
|
104
118
|
end
|
105
119
|
|
106
120
|
[:map, :reduce].each do |type|
|
@@ -223,24 +237,20 @@ describe Riak::MapReduce do
|
|
223
237
|
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
224
238
|
end
|
225
239
|
|
226
|
-
it "should
|
227
|
-
@
|
228
|
-
@mr.run
|
229
|
-
end
|
230
|
-
|
231
|
-
it "should vivify JSON responses" do
|
232
|
-
@http.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '[{"key":"value"}]')
|
233
|
-
@mr.run.should == [{"key" => "value"}]
|
240
|
+
it "should submit the query to the backend" do
|
241
|
+
@backend.should_receive(:mapred).with(@mr).and_return([])
|
242
|
+
@mr.run.should == []
|
234
243
|
end
|
235
244
|
|
236
|
-
it "should
|
237
|
-
|
238
|
-
@
|
239
|
-
@mr.run
|
245
|
+
it "should pass the given block to the backend for streaming" do
|
246
|
+
arr = []
|
247
|
+
@backend.should_receive(:mapred).with(@mr).and_yield("foo").and_yield("bar")
|
248
|
+
@mr.run {|v| arr << v }
|
249
|
+
arr.should == ["foo", "bar"]
|
240
250
|
end
|
241
251
|
|
242
252
|
it "should interpret failed requests with JSON content-types as map reduce errors" do
|
243
|
-
@
|
253
|
+
@backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
|
244
254
|
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
245
255
|
begin
|
246
256
|
@mr.run
|
@@ -252,143 +262,8 @@ describe Riak::MapReduce do
|
|
252
262
|
end
|
253
263
|
|
254
264
|
it "should re-raise non-JSON error responses" do
|
255
|
-
@
|
265
|
+
@backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
|
256
266
|
lambda { @mr.run }.should raise_error(Riak::FailedRequest)
|
257
267
|
end
|
258
268
|
end
|
259
269
|
end
|
260
|
-
|
261
|
-
describe Riak::MapReduce::Phase do
|
262
|
-
before :each do
|
263
|
-
@fun = "function(v,_,_){ return v['values'][0]['data']; }"
|
264
|
-
end
|
265
|
-
|
266
|
-
it "should initialize with a type and a function" do
|
267
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun, :language => "javascript")
|
268
|
-
phase.type.should == :map
|
269
|
-
phase.function.should == @fun
|
270
|
-
phase.language.should == "javascript"
|
271
|
-
end
|
272
|
-
|
273
|
-
it "should initialize with a type and an MF" do
|
274
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"], :language => "erlang")
|
275
|
-
phase.type.should == :map
|
276
|
-
phase.function.should == ["module", "function"]
|
277
|
-
phase.language.should == "erlang"
|
278
|
-
end
|
279
|
-
|
280
|
-
it "should initialize with a type and a bucket/key" do
|
281
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "funs", :key => "awesome_map"}, :language => "javascript")
|
282
|
-
phase.type.should == :map
|
283
|
-
phase.function.should == {:bucket => "funs", :key => "awesome_map"}
|
284
|
-
phase.language.should == "javascript"
|
285
|
-
end
|
286
|
-
|
287
|
-
it "should assume the language is erlang when the function is an array" do
|
288
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"])
|
289
|
-
phase.language.should == "erlang"
|
290
|
-
end
|
291
|
-
|
292
|
-
it "should assume the language is javascript when the function is a string" do
|
293
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun)
|
294
|
-
phase.language.should == "javascript"
|
295
|
-
end
|
296
|
-
|
297
|
-
it "should assume the language is javascript when the function is a hash" do
|
298
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "jobs", :key => "awesome_map"})
|
299
|
-
phase.language.should == "javascript"
|
300
|
-
end
|
301
|
-
|
302
|
-
it "should accept a WalkSpec for the function when a link phase" do
|
303
|
-
phase = Riak::MapReduce::Phase.new(:type => :link, :function => Riak::WalkSpec.new({}))
|
304
|
-
phase.function.should be_kind_of(Riak::WalkSpec)
|
305
|
-
end
|
306
|
-
|
307
|
-
it "should raise an error if a WalkSpec is given for a phase type other than :link" do
|
308
|
-
lambda { Riak::MapReduce::Phase.new(:type => :map, :function => Riak::WalkSpec.new({})) }.should raise_error(ArgumentError)
|
309
|
-
end
|
310
|
-
|
311
|
-
describe "converting to JSON for the job" do
|
312
|
-
before :each do
|
313
|
-
@phase = Riak::MapReduce::Phase.new(:type => :map, :function => "")
|
314
|
-
end
|
315
|
-
|
316
|
-
[:map, :reduce].each do |type|
|
317
|
-
describe "when a #{type} phase" do
|
318
|
-
before :each do
|
319
|
-
@phase.type = type
|
320
|
-
end
|
321
|
-
|
322
|
-
it "should be an object with a single key of '#{type}'" do
|
323
|
-
@phase.to_json.should =~ /^\{"#{type}":/
|
324
|
-
end
|
325
|
-
|
326
|
-
it "should include the language" do
|
327
|
-
@phase.to_json.should =~ /"language":/
|
328
|
-
end
|
329
|
-
|
330
|
-
it "should include the keep value" do
|
331
|
-
@phase.to_json.should =~ /"keep":false/
|
332
|
-
@phase.keep = true
|
333
|
-
@phase.to_json.should =~ /"keep":true/
|
334
|
-
end
|
335
|
-
|
336
|
-
it "should include the function source when the function is a source string" do
|
337
|
-
@phase.function = "function(v,_,_){ return v; }"
|
338
|
-
@phase.to_json.should include(@phase.function)
|
339
|
-
@phase.to_json.should =~ /"source":/
|
340
|
-
end
|
341
|
-
|
342
|
-
it "should include the function name when the function is not a lambda" do
|
343
|
-
@phase.function = "Riak.mapValues"
|
344
|
-
@phase.to_json.should include('"name":"Riak.mapValues"')
|
345
|
-
@phase.to_json.should_not include('"source"')
|
346
|
-
end
|
347
|
-
|
348
|
-
it "should include the bucket and key when referring to a stored function" do
|
349
|
-
@phase.function = {:bucket => "design", :key => "wordcount_map"}
|
350
|
-
@phase.to_json.should include('"bucket":"design"')
|
351
|
-
@phase.to_json.should include('"key":"wordcount_map"')
|
352
|
-
end
|
353
|
-
|
354
|
-
it "should include the module and function when invoking an Erlang function" do
|
355
|
-
@phase.function = ["riak_mapreduce", "mapreduce_fun"]
|
356
|
-
@phase.to_json.should include('"module":"riak_mapreduce"')
|
357
|
-
@phase.to_json.should include('"function":"mapreduce_fun"')
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
describe "when a link phase" do
|
363
|
-
before :each do
|
364
|
-
@phase.type = :link
|
365
|
-
@phase.function = {}
|
366
|
-
end
|
367
|
-
|
368
|
-
it "should be an object of a single key 'link'" do
|
369
|
-
@phase.to_json.should =~ /^\{"link":/
|
370
|
-
end
|
371
|
-
|
372
|
-
it "should include the bucket" do
|
373
|
-
@phase.to_json.should =~ /"bucket":"_"/
|
374
|
-
@phase.function[:bucket] = "foo"
|
375
|
-
@phase.to_json.should =~ /"bucket":"foo"/
|
376
|
-
end
|
377
|
-
|
378
|
-
it "should include the tag" do
|
379
|
-
@phase.to_json.should =~ /"tag":"_"/
|
380
|
-
@phase.function[:tag] = "parent"
|
381
|
-
@phase.to_json.should =~ /"tag":"parent"/
|
382
|
-
end
|
383
|
-
|
384
|
-
it "should include the keep value" do
|
385
|
-
@phase.to_json.should =~ /"keep":false/
|
386
|
-
@phase.keep = true
|
387
|
-
@phase.to_json.should =~ /"keep":true/
|
388
|
-
@phase.keep = false
|
389
|
-
@phase.function[:keep] = true
|
390
|
-
@phase.to_json.should =~ /"keep":true/
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
data/spec/riak/object_spec.rb
CHANGED
@@ -103,7 +103,6 @@ describe Riak::RObject do
|
|
103
103
|
@payload = Marshal.dump({"foo" => "bar"})
|
104
104
|
end
|
105
105
|
|
106
|
-
|
107
106
|
it "should dump via Marshal" do
|
108
107
|
@object.serialize({"foo" => "bar"}).should == @payload
|
109
108
|
end
|
@@ -172,88 +171,6 @@ describe Riak::RObject do
|
|
172
171
|
end
|
173
172
|
end
|
174
173
|
|
175
|
-
describe "loading data from the response" do
|
176
|
-
before :each do
|
177
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
178
|
-
end
|
179
|
-
|
180
|
-
it "should load the content type" do
|
181
|
-
@object.load({:headers => {"content-type" => ["application/json"]}})
|
182
|
-
@object.content_type.should == "application/json"
|
183
|
-
end
|
184
|
-
|
185
|
-
it "should load the body data" do
|
186
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
|
187
|
-
@object.raw_data.should be_present
|
188
|
-
@object.data.should be_present
|
189
|
-
end
|
190
|
-
|
191
|
-
it "should handle raw data properly" do
|
192
|
-
@object.should_not_receive(:deserialize) # optimize for the raw_data case, don't penalize people for using raw_data
|
193
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => body = '{"foo":"bar"}'})
|
194
|
-
@object.raw_data.should == body
|
195
|
-
end
|
196
|
-
|
197
|
-
it "should deserialize the body data" do
|
198
|
-
@object.should_receive(:deserialize).with("{}").and_return({})
|
199
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
|
200
|
-
@object.data.should == {}
|
201
|
-
end
|
202
|
-
|
203
|
-
it "should leave the object data unchanged if the response body is blank" do
|
204
|
-
@object.data = "Original data"
|
205
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => ""})
|
206
|
-
@object.data.should == "Original data"
|
207
|
-
end
|
208
|
-
|
209
|
-
it "should load the vclock from the headers" do
|
210
|
-
@object.load({:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
|
211
|
-
@object.vclock.should == "somereallylongbase64string=="
|
212
|
-
end
|
213
|
-
|
214
|
-
it "should load links from the headers" do
|
215
|
-
@object.load({:headers => {"content-type" => ["application/json"], "link" => ['</riak/bar>; rel="up"']}, :body => "{}"})
|
216
|
-
@object.links.should have(1).item
|
217
|
-
@object.links.first.url.should == "/riak/bar"
|
218
|
-
@object.links.first.rel.should == "up"
|
219
|
-
end
|
220
|
-
|
221
|
-
it "should load the ETag from the headers" do
|
222
|
-
@object.load({:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
|
223
|
-
@object.etag.should == "32748nvas83572934"
|
224
|
-
end
|
225
|
-
|
226
|
-
it "should load the modified date from the headers" do
|
227
|
-
time = Time.now
|
228
|
-
@object.load({:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
|
229
|
-
@object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
|
230
|
-
end
|
231
|
-
|
232
|
-
it "should load meta information from the headers" do
|
233
|
-
@object.load({:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
|
234
|
-
@object.meta["some-kind-of-robot"].should == ["for AWESOME"]
|
235
|
-
end
|
236
|
-
|
237
|
-
it "should parse the location header into the key when present" do
|
238
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
|
239
|
-
@object.key.should == "baz"
|
240
|
-
end
|
241
|
-
|
242
|
-
it "should parse and escape the location header into the key when present" do
|
243
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D"]}})
|
244
|
-
@object.key.should == "[baz]"
|
245
|
-
end
|
246
|
-
|
247
|
-
it "should be in conflict when the response code is 300 and the content-type is multipart/mixed" do
|
248
|
-
@object.load({:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
|
249
|
-
@object.should be_conflict
|
250
|
-
end
|
251
|
-
|
252
|
-
it "should unescape the key given in the location header" do
|
253
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
|
254
|
-
@object.key.should == "baz "
|
255
|
-
end
|
256
|
-
end
|
257
174
|
|
258
175
|
describe "instantiating new object from a map reduce operation" do
|
259
176
|
before :each do
|
@@ -355,144 +272,21 @@ describe Riak::RObject do
|
|
355
272
|
end
|
356
273
|
end
|
357
274
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
it "should extract the siblings" do
|
364
|
-
@object.should have(2).siblings
|
365
|
-
siblings = @object.siblings
|
366
|
-
siblings[0].data.should == "bar"
|
367
|
-
siblings[1].data.should == "baz"
|
368
|
-
end
|
369
|
-
|
370
|
-
it "should set the key on both siblings" do
|
371
|
-
@object.siblings.should be_all {|s| s.key == "bar" }
|
372
|
-
end
|
373
|
-
|
374
|
-
it "should set the vclock on both siblings to the merged vclock" do
|
375
|
-
@object.siblings.should be_all {|s| s.vclock == "merged" }
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
describe "headers used for storing the object" do
|
380
|
-
before :each do
|
381
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
382
|
-
end
|
383
|
-
|
384
|
-
it "should include the content type" do
|
385
|
-
@object.content_type = "application/json"
|
386
|
-
@object.store_headers["Content-Type"].should == "application/json"
|
387
|
-
end
|
388
|
-
|
389
|
-
it "should include the vclock when present" do
|
390
|
-
@object.vclock = "123445678990"
|
391
|
-
@object.store_headers["X-Riak-Vclock"].should == "123445678990"
|
392
|
-
end
|
393
|
-
|
394
|
-
it "should exclude the vclock when nil" do
|
395
|
-
@object.vclock = nil
|
396
|
-
@object.store_headers.should_not have_key("X-Riak-Vclock")
|
397
|
-
end
|
398
|
-
|
399
|
-
describe "when conditional PUTs are requested" do
|
400
|
-
before :each do
|
401
|
-
@object.prevent_stale_writes = true
|
402
|
-
end
|
403
|
-
|
404
|
-
it "should include an If-None-Match: * header" do
|
405
|
-
@object.store_headers.should have_key("If-None-Match")
|
406
|
-
@object.store_headers["If-None-Match"].should == "*"
|
407
|
-
end
|
408
|
-
|
409
|
-
it "should include an If-Match header with the etag when an etag is present" do
|
410
|
-
@object.etag = "foobar"
|
411
|
-
@object.store_headers.should have_key("If-Match")
|
412
|
-
@object.store_headers["If-Match"].should == @object.etag
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
describe "when links are defined" do
|
417
|
-
before :each do
|
418
|
-
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
419
|
-
end
|
420
|
-
|
421
|
-
it "should include a Link header with references to other objects" do
|
422
|
-
@object.store_headers.should have_key("Link")
|
423
|
-
@object.store_headers["Link"].should include('</riak/foo/baz>; riaktag="next"')
|
424
|
-
end
|
425
|
-
|
426
|
-
it "should exclude the 'up' link to the bucket from the header" do
|
427
|
-
@object.links << Riak::Link.new("/riak/foo", "up")
|
428
|
-
@object.store_headers.should have_key("Link")
|
429
|
-
@object.store_headers["Link"].should_not include('riaktag="up"')
|
430
|
-
end
|
431
|
-
|
432
|
-
it "should not allow duplicate links" do
|
433
|
-
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
434
|
-
@object.links.length.should == 1
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
it "should exclude the Link header when no links are present" do
|
439
|
-
@object.links = Set.new
|
440
|
-
@object.store_headers.should_not have_key("Link")
|
441
|
-
end
|
442
|
-
|
443
|
-
describe "when meta fields are present" do
|
444
|
-
before :each do
|
445
|
-
@object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
|
446
|
-
end
|
447
|
-
|
448
|
-
it "should include X-Riak-Meta-* headers for each meta key" do
|
449
|
-
@object.store_headers.should have_key("X-Riak-Meta-some-kind-of-robot")
|
450
|
-
@object.store_headers.should have_key("X-Riak-Meta-cold-ones")
|
451
|
-
@object.store_headers.should have_key("X-Riak-Meta-powers")
|
452
|
-
end
|
453
|
-
|
454
|
-
it "should turn non-string meta values into strings" do
|
455
|
-
@object.store_headers["X-Riak-Meta-some-kind-of-robot"].should == "true"
|
456
|
-
@object.store_headers["X-Riak-Meta-cold-ones"].should == "10"
|
457
|
-
end
|
458
|
-
|
459
|
-
it "should leave string meta values unchanged in the header" do
|
460
|
-
@object.store_headers["X-Riak-Meta-powers"].should == "for awesome"
|
461
|
-
end
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
describe "headers used for reloading the object" do
|
466
|
-
before :each do
|
467
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
468
|
-
end
|
469
|
-
|
470
|
-
it "should be blank when the etag and last_modified properties are blank" do
|
471
|
-
@object.etag.should be_blank
|
472
|
-
@object.last_modified.should be_blank
|
473
|
-
@object.reload_headers.should be_blank
|
474
|
-
end
|
475
|
-
|
476
|
-
it "should include the If-None-Match key when the etag is present" do
|
477
|
-
@object.etag = "etag!"
|
478
|
-
@object.reload_headers['If-None-Match'].should == "etag!"
|
479
|
-
end
|
480
|
-
|
481
|
-
it "should include the If-Modified-Since header when the last_modified time is present" do
|
482
|
-
time = Time.now
|
483
|
-
@object.last_modified = time
|
484
|
-
@object.reload_headers['If-Modified-Since'].should == time.httpdate
|
485
|
-
end
|
275
|
+
it "should not allow duplicate links" do
|
276
|
+
@object = Riak::RObject.new(@bucket, "foo")
|
277
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
278
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
279
|
+
@object.links.length.should == 1
|
486
280
|
end
|
487
281
|
|
488
282
|
describe "when storing the object normally" do
|
489
283
|
before :each do
|
490
|
-
@
|
491
|
-
@client.stub!(:
|
284
|
+
@backend = mock("Backend")
|
285
|
+
@client.stub!(:backend).and_return(@backend)
|
492
286
|
@object = Riak::RObject.new(@bucket)
|
493
287
|
@object.content_type = "text/plain"
|
494
288
|
@object.data = "This is some text."
|
495
|
-
@headers = @object.store_headers
|
289
|
+
# @headers = @object.store_headers
|
496
290
|
end
|
497
291
|
|
498
292
|
it "should raise an error when the content_type is blank" do
|
@@ -500,124 +294,49 @@ describe Riak::RObject do
|
|
500
294
|
lambda { @object.content_type = " "; @object.store }.should raise_error(ArgumentError)
|
501
295
|
end
|
502
296
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
@object.store
|
507
|
-
@object.key.should == "somereallylongstring"
|
508
|
-
@object.vclock.should == "areallylonghashvalue"
|
509
|
-
end
|
510
|
-
|
511
|
-
it "should include persistence-tuning parameters in the query string" do
|
512
|
-
@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})
|
513
|
-
@object.store(:dw => 2)
|
514
|
-
end
|
515
|
-
|
516
|
-
it "should escape the bucket name" do
|
517
|
-
@bucket.should_receive(:name).and_return("foo ")
|
518
|
-
@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})
|
519
|
-
@object.store
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
describe "when the object has a key" do
|
524
|
-
before :each do
|
525
|
-
@object.key = "bar"
|
526
|
-
end
|
527
|
-
|
528
|
-
describe "when the content is of a known serializable type" do
|
529
|
-
before :each do
|
530
|
-
@object.content_type = "application/json"
|
531
|
-
@headers = @object.store_headers
|
532
|
-
end
|
533
|
-
|
534
|
-
it "should not serialize content if #raw_data is used" do
|
535
|
-
storing = @object.raw_data = "{this is probably invalid json}}"
|
536
|
-
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, storing, @headers).and_return({:headers => {"x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
537
|
-
@object.should_not_receive(:serialize)
|
538
|
-
@object.should_not_receive(:deserialize)
|
539
|
-
@object.store
|
540
|
-
@object.raw_data.should == storing
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
|
545
|
-
@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})
|
546
|
-
@object.store
|
547
|
-
@object.key.should == "somereallylongstring"
|
548
|
-
@object.vclock.should == "areallylonghashvalue"
|
549
|
-
end
|
550
|
-
|
551
|
-
it "should include persistence-tuning parameters in the query string" do
|
552
|
-
@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})
|
553
|
-
@object.store(:dw => 2)
|
554
|
-
end
|
555
|
-
|
556
|
-
it "should escape the bucket and key names" do
|
557
|
-
@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})
|
558
|
-
@bucket.instance_variable_set(:@name, "foo ")
|
559
|
-
@object.key = "bar/baz"
|
560
|
-
@object.store
|
561
|
-
end
|
297
|
+
it "should pass along quorum parameters and returnbody to the backend" do
|
298
|
+
@backend.should_receive(:store_object).with(@object, false, 3, 2).and_return(true)
|
299
|
+
@object.store(:returnbody => false, :w => 3, :dw => 2)
|
562
300
|
end
|
563
301
|
end
|
564
302
|
|
565
303
|
describe "when reloading the object" do
|
566
304
|
before :each do
|
567
|
-
@
|
568
|
-
@client.stub!(:
|
305
|
+
@backend = mock("Backend")
|
306
|
+
@client.stub!(:backend).and_return(@backend)
|
569
307
|
@object = Riak::RObject.new(@bucket, "bar")
|
570
308
|
@object.vclock = "somereallylongstring"
|
571
|
-
@object.stub!(:reload_headers).and_return({})
|
572
309
|
end
|
573
310
|
|
574
311
|
it "should return without requesting if the key is blank" do
|
575
312
|
@object.key = nil
|
576
|
-
@
|
313
|
+
@backend.should_not_receive(:reload_object)
|
577
314
|
@object.reload
|
578
315
|
end
|
579
316
|
|
580
317
|
it "should return without requesting if the vclock is blank" do
|
581
318
|
@object.vclock = nil
|
582
|
-
@
|
583
|
-
@object.reload
|
584
|
-
end
|
585
|
-
|
586
|
-
it "should make the request if the key is present and the :force option is given" do
|
587
|
-
@http.should_receive(:get).and_return({:headers => {}, :code => 304})
|
588
|
-
@object.reload :force => true
|
589
|
-
end
|
590
|
-
|
591
|
-
it "should pass along the reload_headers" do
|
592
|
-
@headers = {"If-None-Match" => "etag"}
|
593
|
-
@object.should_receive(:reload_headers).and_return(@headers)
|
594
|
-
@http.should_receive(:get).with([200,304], "/riak/", "foo", "bar", {}, @headers).and_return({:code => 304})
|
319
|
+
@backend.should_not_receive(:reload_object)
|
595
320
|
@object.reload
|
596
321
|
end
|
597
322
|
|
598
|
-
it "should
|
599
|
-
@
|
600
|
-
@object.should_not_receive(:load)
|
323
|
+
it "should reload the object if the key is present" do
|
324
|
+
@backend.should_receive(:reload_object).with(@object, nil).and_return(@object)
|
601
325
|
@object.reload
|
602
326
|
end
|
603
327
|
|
604
|
-
it "should
|
605
|
-
@
|
606
|
-
@object.
|
607
|
-
lambda { @object.reload }.should raise_error(Riak::FailedRequest)
|
328
|
+
it "should pass along the requested R quorum" do
|
329
|
+
@backend.should_receive(:reload_object).with(@object, 2).and_return(@object)
|
330
|
+
@object.reload :r => 2
|
608
331
|
end
|
609
|
-
|
610
|
-
it "should
|
611
|
-
@
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
@bucket.should_receive(:name).and_return("some/deep/path")
|
618
|
-
@object.key = "another/deep/path"
|
619
|
-
@http.should_receive(:get).with([200,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
|
620
|
-
@object.reload
|
332
|
+
|
333
|
+
it "should disable matching conditions if the key is present and the :force option is given" do
|
334
|
+
@backend.should_receive(:reload_object) do |obj, _|
|
335
|
+
obj.etag.should be_nil
|
336
|
+
obj.last_modified.should be_nil
|
337
|
+
obj
|
338
|
+
end
|
339
|
+
@object.reload :force => true
|
621
340
|
end
|
622
341
|
end
|
623
342
|
|
@@ -627,70 +346,37 @@ describe Riak::RObject do
|
|
627
346
|
@client.stub!(:http).and_return(@http)
|
628
347
|
@client.stub!(:bucket).and_return(@bucket)
|
629
348
|
@object = Riak::RObject.new(@bucket, "bar")
|
630
|
-
@body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
|
631
|
-
end
|
632
|
-
|
633
|
-
it "should issue a GET request to the given walk spec" do
|
634
|
-
@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")
|
635
|
-
@object.walk(nil,"next",true)
|
636
349
|
end
|
637
350
|
|
638
|
-
it "should
|
639
|
-
@http.
|
640
|
-
|
641
|
-
results.should be_kind_of(Array)
|
642
|
-
results.first.should be_kind_of(Array)
|
643
|
-
obj = results.first.first
|
644
|
-
obj.should be_kind_of(Riak::RObject)
|
645
|
-
obj.content_type.should == "text/plain"
|
646
|
-
obj.key.should == "baz"
|
647
|
-
obj.bucket.should == @bucket
|
648
|
-
end
|
649
|
-
|
650
|
-
it "should assign the bucket for newly parsed objects" do
|
651
|
-
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
652
|
-
@client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
|
653
|
-
@object.walk(nil,"next",true)
|
654
|
-
end
|
655
|
-
|
656
|
-
it "should escape the bucket, key and link specs" do
|
657
|
-
@object.key = "bar/baz"
|
658
|
-
@bucket.should_receive(:name).and_return("quin/quux")
|
659
|
-
@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")
|
660
|
-
@object.walk(:tag => "next/2", :keep => true)
|
351
|
+
it "should normalize the walk specs and submit the link-walking request to the HTTP backend" do
|
352
|
+
@http.should_receive(:link_walk).with(@object, [instance_of(Riak::WalkSpec)]).and_return([])
|
353
|
+
@object.walk(nil,"next",true).should == []
|
661
354
|
end
|
662
355
|
end
|
663
356
|
|
664
357
|
describe "when deleting" do
|
665
358
|
before :each do
|
666
|
-
@
|
667
|
-
@client.stub!(:
|
359
|
+
@backend = mock("Backend")
|
360
|
+
@client.stub!(:backend).and_return(@backend)
|
668
361
|
@object = Riak::RObject.new(@bucket, "bar")
|
669
362
|
end
|
670
363
|
|
671
364
|
it "should make a DELETE request to the Riak server and freeze the object" do
|
672
|
-
@
|
365
|
+
@backend.should_receive(:delete_object).with(@bucket, "bar", nil)
|
673
366
|
@object.delete
|
674
367
|
@object.should be_frozen
|
675
368
|
end
|
676
369
|
|
677
370
|
it "should do nothing when the key is blank" do
|
678
|
-
@
|
371
|
+
@backend.should_not_receive(:delete_object)
|
679
372
|
@object.key = nil
|
680
373
|
@object.delete
|
681
374
|
end
|
682
375
|
|
683
376
|
it "should pass through a failed request exception" do
|
684
|
-
@
|
377
|
+
@backend.should_receive(:delete_object).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
|
685
378
|
lambda { @object.delete }.should raise_error(Riak::FailedRequest)
|
686
379
|
end
|
687
|
-
|
688
|
-
it "should escape the bucket and key names" do
|
689
|
-
@object.key = "deep/path"
|
690
|
-
@bucket.should_receive(:name).and_return("bucket spaces")
|
691
|
-
@http.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
|
692
|
-
@object.delete
|
693
|
-
end
|
694
380
|
end
|
695
381
|
|
696
382
|
it "should not convert to link without a tag" do
|