spec/riak/object_spec.rb in riak-client-0.8.3 vs spec/riak/object_spec.rb in riak-client-0.9.0.beta
- old
+ new
@@ -101,11 +101,10 @@
before :each do
@object.content_type = "application/x-ruby-marshal"
@payload = Marshal.dump({"foo" => "bar"})
end
-
it "should dump via Marshal" do
@object.serialize({"foo" => "bar"}).should == @payload
end
it "should load from Marshal" do
@@ -170,93 +169,11 @@
@object.data.should == value
end
end
end
- describe "loading data from the response" do
- before :each do
- @object = Riak::RObject.new(@bucket, "bar")
- end
- it "should load the content type" do
- @object.load({:headers => {"content-type" => ["application/json"]}})
- @object.content_type.should == "application/json"
- end
-
- it "should load the body data" do
- @object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
- @object.raw_data.should be_present
- @object.data.should be_present
- end
-
- it "should handle raw data properly" do
- @object.should_not_receive(:deserialize) # optimize for the raw_data case, don't penalize people for using raw_data
- @object.load({:headers => {"content-type" => ["application/json"]}, :body => body = '{"foo":"bar"}'})
- @object.raw_data.should == body
- end
-
- it "should deserialize the body data" do
- @object.should_receive(:deserialize).with("{}").and_return({})
- @object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
- @object.data.should == {}
- end
-
- it "should leave the object data unchanged if the response body is blank" do
- @object.data = "Original data"
- @object.load({:headers => {"content-type" => ["application/json"]}, :body => ""})
- @object.data.should == "Original data"
- end
-
- it "should load the vclock from the headers" do
- @object.load({:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
- @object.vclock.should == "somereallylongbase64string=="
- end
-
- it "should load links from the headers" do
- @object.load({:headers => {"content-type" => ["application/json"], "link" => ['</riak/bar>; rel="up"']}, :body => "{}"})
- @object.links.should have(1).item
- @object.links.first.url.should == "/riak/bar"
- @object.links.first.rel.should == "up"
- end
-
- it "should load the ETag from the headers" do
- @object.load({:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
- @object.etag.should == "32748nvas83572934"
- end
-
- it "should load the modified date from the headers" do
- time = Time.now
- @object.load({:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
- @object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
- end
-
- it "should load meta information from the headers" do
- @object.load({:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
- @object.meta["some-kind-of-robot"].should == ["for AWESOME"]
- end
-
- it "should parse the location header into the key when present" do
- @object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
- @object.key.should == "baz"
- end
-
- it "should parse and escape the location header into the key when present" do
- @object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D"]}})
- @object.key.should == "[baz]"
- end
-
- it "should be in conflict when the response code is 300 and the content-type is multipart/mixed" do
- @object.load({:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
- @object.should be_conflict
- end
-
- it "should unescape the key given in the location header" do
- @object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
- @object.key.should == "baz "
- end
- end
-
describe "instantiating new object from a map reduce operation" do
before :each do
@client.stub!(:[]).and_return(@bucket)
@sample_response = [
@@ -353,344 +270,113 @@
@object.siblings.should be_all {|s| s.vclock == @object.vclock }
end
end
end
- describe "extracting siblings" do
- before :each do
- @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"})
- end
-
- it "should extract the siblings" do
- @object.should have(2).siblings
- siblings = @object.siblings
- siblings[0].data.should == "bar"
- siblings[1].data.should == "baz"
- end
-
- it "should set the key on both siblings" do
- @object.siblings.should be_all {|s| s.key == "bar" }
- end
-
- it "should set the vclock on both siblings to the merged vclock" do
- @object.siblings.should be_all {|s| s.vclock == "merged" }
- end
+ it "should not allow duplicate links" do
+ @object = Riak::RObject.new(@bucket, "foo")
+ @object.links << Riak::Link.new("/riak/foo/baz", "next")
+ @object.links << Riak::Link.new("/riak/foo/baz", "next")
+ @object.links.length.should == 1
end
- describe "headers used for storing the object" do
- before :each do
- @object = Riak::RObject.new(@bucket, "bar")
- end
-
- it "should include the content type" do
- @object.content_type = "application/json"
- @object.store_headers["Content-Type"].should == "application/json"
- end
-
- it "should include the vclock when present" do
- @object.vclock = "123445678990"
- @object.store_headers["X-Riak-Vclock"].should == "123445678990"
- end
-
- it "should exclude the vclock when nil" do
- @object.vclock = nil
- @object.store_headers.should_not have_key("X-Riak-Vclock")
- end
-
- describe "when conditional PUTs are requested" do
- before :each do
- @object.prevent_stale_writes = true
- end
-
- it "should include an If-None-Match: * header" do
- @object.store_headers.should have_key("If-None-Match")
- @object.store_headers["If-None-Match"].should == "*"
- end
-
- it "should include an If-Match header with the etag when an etag is present" do
- @object.etag = "foobar"
- @object.store_headers.should have_key("If-Match")
- @object.store_headers["If-Match"].should == @object.etag
- end
- end
-
- describe "when links are defined" do
- before :each do
- @object.links << Riak::Link.new("/riak/foo/baz", "next")
- end
-
- it "should include a Link header with references to other objects" do
- @object.store_headers.should have_key("Link")
- @object.store_headers["Link"].should include('</riak/foo/baz>; riaktag="next"')
- end
-
- it "should exclude the 'up' link to the bucket from the header" do
- @object.links << Riak::Link.new("/riak/foo", "up")
- @object.store_headers.should have_key("Link")
- @object.store_headers["Link"].should_not include('riaktag="up"')
- end
-
- it "should not allow duplicate links" do
- @object.links << Riak::Link.new("/riak/foo/baz", "next")
- @object.links.length.should == 1
- end
- end
-
- it "should exclude the Link header when no links are present" do
- @object.links = Set.new
- @object.store_headers.should_not have_key("Link")
- end
-
- describe "when meta fields are present" do
- before :each do
- @object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
- end
-
- it "should include X-Riak-Meta-* headers for each meta key" do
- @object.store_headers.should have_key("X-Riak-Meta-some-kind-of-robot")
- @object.store_headers.should have_key("X-Riak-Meta-cold-ones")
- @object.store_headers.should have_key("X-Riak-Meta-powers")
- end
-
- it "should turn non-string meta values into strings" do
- @object.store_headers["X-Riak-Meta-some-kind-of-robot"].should == "true"
- @object.store_headers["X-Riak-Meta-cold-ones"].should == "10"
- end
-
- it "should leave string meta values unchanged in the header" do
- @object.store_headers["X-Riak-Meta-powers"].should == "for awesome"
- end
- end
- end
-
- describe "headers used for reloading the object" do
- before :each do
- @object = Riak::RObject.new(@bucket, "bar")
- end
-
- it "should be blank when the etag and last_modified properties are blank" do
- @object.etag.should be_blank
- @object.last_modified.should be_blank
- @object.reload_headers.should be_blank
- end
-
- it "should include the If-None-Match key when the etag is present" do
- @object.etag = "etag!"
- @object.reload_headers['If-None-Match'].should == "etag!"
- end
-
- it "should include the If-Modified-Since header when the last_modified time is present" do
- time = Time.now
- @object.last_modified = time
- @object.reload_headers['If-Modified-Since'].should == time.httpdate
- end
- end
-
describe "when storing the object normally" do
before :each do
- @http = mock("HTTPBackend")
- @client.stub!(:http).and_return(@http)
+ @backend = mock("Backend")
+ @client.stub!(:backend).and_return(@backend)
@object = Riak::RObject.new(@bucket)
@object.content_type = "text/plain"
@object.data = "This is some text."
- @headers = @object.store_headers
+ # @headers = @object.store_headers
end
it "should raise an error when the content_type is blank" do
lambda { @object.content_type = nil; @object.store }.should raise_error(ArgumentError)
lambda { @object.content_type = " "; @object.store }.should raise_error(ArgumentError)
end
- describe "when the object has no key" do
- it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
- @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})
- @object.store
- @object.key.should == "somereallylongstring"
- @object.vclock.should == "areallylonghashvalue"
- end
-
- it "should include persistence-tuning parameters in the query string" do
- @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})
- @object.store(:dw => 2)
- end
-
- it "should escape the bucket name" do
- @bucket.should_receive(:name).and_return("foo ")
- @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})
- @object.store
- end
+ it "should pass along quorum parameters and returnbody to the backend" do
+ @backend.should_receive(:store_object).with(@object, false, 3, 2).and_return(true)
+ @object.store(:returnbody => false, :w => 3, :dw => 2)
end
-
- describe "when the object has a key" do
- before :each do
- @object.key = "bar"
- end
-
- describe "when the content is of a known serializable type" do
- before :each do
- @object.content_type = "application/json"
- @headers = @object.store_headers
- end
-
- it "should not serialize content if #raw_data is used" do
- storing = @object.raw_data = "{this is probably invalid json}}"
- @http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, storing, @headers).and_return({:headers => {"x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
- @object.should_not_receive(:serialize)
- @object.should_not_receive(:deserialize)
- @object.store
- @object.raw_data.should == storing
- end
- end
-
- it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
- @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})
- @object.store
- @object.key.should == "somereallylongstring"
- @object.vclock.should == "areallylonghashvalue"
- end
-
- it "should include persistence-tuning parameters in the query string" do
- @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})
- @object.store(:dw => 2)
- end
-
- it "should escape the bucket and key names" do
- @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})
- @bucket.instance_variable_set(:@name, "foo ")
- @object.key = "bar/baz"
- @object.store
- end
- end
end
describe "when reloading the object" do
before :each do
- @http = mock("HTTPBackend")
- @client.stub!(:http).and_return(@http)
+ @backend = mock("Backend")
+ @client.stub!(:backend).and_return(@backend)
@object = Riak::RObject.new(@bucket, "bar")
@object.vclock = "somereallylongstring"
- @object.stub!(:reload_headers).and_return({})
end
it "should return without requesting if the key is blank" do
@object.key = nil
- @http.should_not_receive(:get)
+ @backend.should_not_receive(:reload_object)
@object.reload
end
it "should return without requesting if the vclock is blank" do
@object.vclock = nil
- @http.should_not_receive(:get)
+ @backend.should_not_receive(:reload_object)
@object.reload
end
- it "should make the request if the key is present and the :force option is given" do
- @http.should_receive(:get).and_return({:headers => {}, :code => 304})
- @object.reload :force => true
- end
-
- it "should pass along the reload_headers" do
- @headers = {"If-None-Match" => "etag"}
- @object.should_receive(:reload_headers).and_return(@headers)
- @http.should_receive(:get).with([200,304], "/riak/", "foo", "bar", {}, @headers).and_return({:code => 304})
+ it "should reload the object if the key is present" do
+ @backend.should_receive(:reload_object).with(@object, nil).and_return(@object)
@object.reload
end
- it "should return without modifying the object if the response is 304 Not Modified" do
- @http.should_receive(:get).and_return({:code => 304})
- @object.should_not_receive(:load)
- @object.reload
+ it "should pass along the requested R quorum" do
+ @backend.should_receive(:reload_object).with(@object, 2).and_return(@object)
+ @object.reload :r => 2
end
-
- it "should raise an exception when the response code is not 200 or 304" do
- @http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
- @object.should_not_receive(:load)
- lambda { @object.reload }.should raise_error(Riak::FailedRequest)
+
+ it "should disable matching conditions if the key is present and the :force option is given" do
+ @backend.should_receive(:reload_object) do |obj, _|
+ obj.etag.should be_nil
+ obj.last_modified.should be_nil
+ obj
+ end
+ @object.reload :force => true
end
-
- it "should include 300 in valid responses if the bucket has allow_mult set" do
- @object.bucket.should_receive(:allow_mult).and_return(true)
- @http.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {}).and_return({:code => 304})
- @object.reload
- end
-
- it "should escape the bucket and key names" do
- @bucket.should_receive(:name).and_return("some/deep/path")
- @object.key = "another/deep/path"
- @http.should_receive(:get).with([200,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
- @object.reload
- end
end
describe "walking from the object to linked objects" do
before :each do
@http = mock("HTTPBackend")
@client.stub!(:http).and_return(@http)
@client.stub!(:bucket).and_return(@bucket)
@object = Riak::RObject.new(@bucket, "bar")
- @body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
end
- it "should issue a GET request to the given walk spec" do
- @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")
- @object.walk(nil,"next",true)
+ it "should normalize the walk specs and submit the link-walking request to the HTTP backend" do
+ @http.should_receive(:link_walk).with(@object, [instance_of(Riak::WalkSpec)]).and_return([])
+ @object.walk(nil,"next",true).should == []
end
-
- it "should parse the results into arrays of objects" do
- @http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
- results = @object.walk(nil,"next",true)
- results.should be_kind_of(Array)
- results.first.should be_kind_of(Array)
- obj = results.first.first
- obj.should be_kind_of(Riak::RObject)
- obj.content_type.should == "text/plain"
- obj.key.should == "baz"
- obj.bucket.should == @bucket
- end
-
- it "should assign the bucket for newly parsed objects" do
- @http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
- @client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
- @object.walk(nil,"next",true)
- end
-
- it "should escape the bucket, key and link specs" do
- @object.key = "bar/baz"
- @bucket.should_receive(:name).and_return("quin/quux")
- @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")
- @object.walk(:tag => "next/2", :keep => true)
- end
end
describe "when deleting" do
before :each do
- @http = mock("HTTPBackend")
- @client.stub!(:http).and_return(@http)
+ @backend = mock("Backend")
+ @client.stub!(:backend).and_return(@backend)
@object = Riak::RObject.new(@bucket, "bar")
end
it "should make a DELETE request to the Riak server and freeze the object" do
- @http.should_receive(:delete).with([204,404], "/riak/", "foo", "bar", {},{}).and_return({:code => 204, :headers => {}})
+ @backend.should_receive(:delete_object).with(@bucket, "bar", nil)
@object.delete
@object.should be_frozen
end
it "should do nothing when the key is blank" do
- @http.should_not_receive(:delete)
+ @backend.should_not_receive(:delete_object)
@object.key = nil
@object.delete
end
it "should pass through a failed request exception" do
- @http.should_receive(:delete).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
+ @backend.should_receive(:delete_object).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
lambda { @object.delete }.should raise_error(Riak::FailedRequest)
- end
-
- it "should escape the bucket and key names" do
- @object.key = "deep/path"
- @bucket.should_receive(:name).and_return("bucket spaces")
- @http.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
- @object.delete
end
end
it "should not convert to link without a tag" do
@object = Riak::RObject.new(@bucket, "bar")