riak-client 0.8.3 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- @http = mock("HTTPBackend")
20
- @client.stub!(:http).and_return(@http)
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 issue POST request to the mapred endpoint" do
227
- @http.should_receive(:post).with(200, "/mapred", @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "[]"})
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 return the full response hash for non-JSON responses" do
237
- response = {:code => 200, :headers => {'content-type' => ["text/plain"]}, :body => 'This is some text.'}
238
- @http.stub!(:post).and_return(response)
239
- @mr.run.should == response
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
- @http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
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
- @http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
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
@@ -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
- describe "extracting siblings" do
359
- before :each do
360
- @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"})
361
- end
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
- @http = mock("HTTPBackend")
491
- @client.stub!(:http).and_return(@http)
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
- describe "when the object has no key" do
504
- it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
505
- @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})
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
- @http = mock("HTTPBackend")
568
- @client.stub!(:http).and_return(@http)
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
- @http.should_not_receive(:get)
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
- @http.should_not_receive(:get)
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 return without modifying the object if the response is 304 Not Modified" do
599
- @http.should_receive(:get).and_return({:code => 304})
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 raise an exception when the response code is not 200 or 304" do
605
- @http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
606
- @object.should_not_receive(:load)
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 include 300 in valid responses if the bucket has allow_mult set" do
611
- @object.bucket.should_receive(:allow_mult).and_return(true)
612
- @http.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {}).and_return({:code => 304})
613
- @object.reload
614
- end
615
-
616
- it "should escape the bucket and key names" do
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 parse the results into arrays of objects" do
639
- @http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
640
- results = @object.walk(nil,"next",true)
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
- @http = mock("HTTPBackend")
667
- @client.stub!(:http).and_return(@http)
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
- @http.should_receive(:delete).with([204,404], "/riak/", "foo", "bar", {},{}).and_return({:code => 204, :headers => {}})
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
- @http.should_not_receive(:delete)
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
- @http.should_receive(:delete).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
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