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.
@@ -33,4 +33,11 @@ describe Riak::Util::Escape do
33
33
  it "should convert the bucket or key to a string before escaping" do
34
34
  @object.escape(125).should == '125'
35
35
  end
36
+
37
+ it "should unescape escaped strings" do
38
+ @object.unescape("some%20string").should == "some string"
39
+ @object.unescape("another%5Eone").should == "another^one"
40
+ @object.unescape("bracket%5Bone").should == "bracket[one"
41
+ @object.unescape("some%2Finner%2Fpath").should == "some/inner/path"
42
+ end
36
43
  end
@@ -0,0 +1,218 @@
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::HTTPBackend::ObjectMethods do
17
+ before :each do
18
+ @client = Riak::Client.new
19
+ @backend = Riak::Client::HTTPBackend.new(@client)
20
+ @object = Riak::RObject.new(@bucket, "bar")
21
+ end
22
+
23
+ describe "loading object data from the response" do
24
+ it "should load the content type" do
25
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"]}})
26
+ @object.content_type.should == "application/json"
27
+ end
28
+
29
+ it "should load the body data" do
30
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
31
+ @object.raw_data.should be_present
32
+ @object.data.should be_present
33
+ end
34
+
35
+ it "should handle raw data properly" do
36
+ @object.should_not_receive(:deserialize) # optimize for the raw_data case, don't penalize people for using raw_data
37
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"]}, :body => body = '{"foo":"bar"}'})
38
+ @object.raw_data.should == body
39
+ end
40
+
41
+ it "should deserialize the body data" do
42
+ @object.should_receive(:deserialize).with("{}").and_return({})
43
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"]}, :body => "{}"})
44
+ @object.data.should == {}
45
+ end
46
+
47
+ it "should leave the object data unchanged if the response body is blank" do
48
+ @object.data = "Original data"
49
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"]}, :body => ""})
50
+ @object.data.should == "Original data"
51
+ end
52
+
53
+ it "should load the vclock from the headers" do
54
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
55
+ @object.vclock.should == "somereallylongbase64string=="
56
+ end
57
+
58
+ it "should load links from the headers" do
59
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "link" => ['</riak/bar>; rel="up"']}, :body => "{}"})
60
+ @object.links.should have(1).item
61
+ @object.links.first.url.should == "/riak/bar"
62
+ @object.links.first.rel.should == "up"
63
+ end
64
+
65
+ it "should load the ETag from the headers" do
66
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
67
+ @object.etag.should == "32748nvas83572934"
68
+ end
69
+
70
+ it "should load the modified date from the headers" do
71
+ time = Time.now
72
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
73
+ @object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
74
+ end
75
+
76
+ it "should load meta information from the headers" do
77
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
78
+ @object.meta["some-kind-of-robot"].should == ["for AWESOME"]
79
+ end
80
+
81
+ it "should parse the location header into the key when present" do
82
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
83
+ @object.key.should == "baz"
84
+ end
85
+
86
+ it "should parse and escape the location header into the key when present" do
87
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D"]}})
88
+ @object.key.should == "[baz]"
89
+ end
90
+
91
+ it "should be in conflict when the response code is 300 and the content-type is multipart/mixed" do
92
+ @backend.load_object(@object, {:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
93
+ @object.should be_conflict
94
+ end
95
+
96
+ it "should unescape the key given in the location header" do
97
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
98
+ @object.key.should == "baz "
99
+ end
100
+
101
+ describe "extracting siblings" do
102
+ before :each do
103
+ @backend.load_object(@object, {: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"})
104
+ end
105
+
106
+ it "should extract the siblings" do
107
+ @object.should have(2).siblings
108
+ siblings = @object.siblings
109
+ siblings[0].data.should == "bar"
110
+ siblings[1].data.should == "baz"
111
+ end
112
+
113
+ it "should set the key on both siblings" do
114
+ @object.siblings.should be_all {|s| s.key == "bar" }
115
+ end
116
+
117
+ it "should set the vclock on both siblings to the merged vclock" do
118
+ @object.siblings.should be_all {|s| s.vclock == "merged" }
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "headers used for storing the object" do
124
+ it "should include the content type" do
125
+ @object.content_type = "application/json"
126
+ @backend.store_headers(@object)["Content-Type"].should == "application/json"
127
+ end
128
+
129
+ it "should include the vclock when present" do
130
+ @object.vclock = "123445678990"
131
+ @backend.store_headers(@object)["X-Riak-Vclock"].should == "123445678990"
132
+ end
133
+
134
+ it "should exclude the vclock when nil" do
135
+ @object.vclock = nil
136
+ @backend.store_headers(@object).should_not have_key("X-Riak-Vclock")
137
+ end
138
+
139
+ describe "when conditional PUTs are requested" do
140
+ before :each do
141
+ @object.prevent_stale_writes = true
142
+ end
143
+
144
+ it "should include an If-None-Match: * header" do
145
+ @backend.store_headers(@object).should have_key("If-None-Match")
146
+ @backend.store_headers(@object)["If-None-Match"].should == "*"
147
+ end
148
+
149
+ it "should include an If-Match header with the etag when an etag is present" do
150
+ @object.etag = "foobar"
151
+ @backend.store_headers(@object).should have_key("If-Match")
152
+ @backend.store_headers(@object)["If-Match"].should == @object.etag
153
+ end
154
+ end
155
+
156
+ describe "when links are defined" do
157
+ before :each do
158
+ @object.links << Riak::Link.new("/riak/foo/baz", "next")
159
+ end
160
+
161
+ it "should include a Link header with references to other objects" do
162
+ @backend.store_headers(@object).should have_key("Link")
163
+ @backend.store_headers(@object)["Link"].should include('</riak/foo/baz>; riaktag="next"')
164
+ end
165
+
166
+ it "should exclude the 'up' link to the bucket from the header" do
167
+ @object.links << Riak::Link.new("/riak/foo", "up")
168
+ @backend.store_headers(@object).should have_key("Link")
169
+ @backend.store_headers(@object)["Link"].should_not include('riaktag="up"')
170
+ end
171
+ end
172
+
173
+ it "should exclude the Link header when no links are present" do
174
+ @object.links = Set.new
175
+ @backend.store_headers(@object).should_not have_key("Link")
176
+ end
177
+
178
+ describe "when meta fields are present" do
179
+ before :each do
180
+ @object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
181
+ end
182
+
183
+ it "should include X-Riak-Meta-* headers for each meta key" do
184
+ @backend.store_headers(@object).should have_key("X-Riak-Meta-some-kind-of-robot")
185
+ @backend.store_headers(@object).should have_key("X-Riak-Meta-cold-ones")
186
+ @backend.store_headers(@object).should have_key("X-Riak-Meta-powers")
187
+ end
188
+
189
+ it "should turn non-string meta values into strings" do
190
+ @backend.store_headers(@object)["X-Riak-Meta-some-kind-of-robot"].should == "true"
191
+ @backend.store_headers(@object)["X-Riak-Meta-cold-ones"].should == "10"
192
+ end
193
+
194
+ it "should leave string meta values unchanged in the header" do
195
+ @backend.store_headers(@object)["X-Riak-Meta-powers"].should == "for awesome"
196
+ end
197
+ end
198
+ end
199
+
200
+ describe "headers used for reloading the object" do
201
+ it "should be blank when the etag and last_modified properties are blank" do
202
+ @object.etag.should be_blank
203
+ @object.last_modified.should be_blank
204
+ @backend.reload_headers(@object).should be_blank
205
+ end
206
+
207
+ it "should include the If-None-Match key when the etag is present" do
208
+ @object.etag = "etag!"
209
+ @backend.reload_headers(@object)['If-None-Match'].should == "etag!"
210
+ end
211
+
212
+ it "should include the If-Modified-Since header when the last_modified time is present" do
213
+ time = Time.now
214
+ @object.last_modified = time
215
+ @backend.reload_headers(@object)['If-Modified-Since'].should == time.httpdate
216
+ end
217
+ end
218
+ end
@@ -17,6 +17,7 @@ describe Riak::Client::HTTPBackend do
17
17
  before :each do
18
18
  @client = Riak::Client.new
19
19
  @backend = Riak::Client::HTTPBackend.new(@client)
20
+ @backend.instance_variable_set(:@server_config, {})
20
21
  end
21
22
 
22
23
  it "should take the Riak::Client when creating" do
@@ -28,104 +29,242 @@ describe Riak::Client::HTTPBackend do
28
29
  @backend.client.should == @client
29
30
  end
30
31
 
31
- it "should generate default headers for requests based on the client settings" do
32
- @client.client_id = "testing"
33
- @backend.default_headers.should == {"X-Riak-ClientId" => "testing", "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5"}
34
- end
32
+ context "pinging the server" do
33
+ it "should succeed on 200" do
34
+ @backend.should_receive(:get).with(200, "/ping", {}, {}).and_return({:code => 200, :body => "OK"})
35
+ @backend.ping.should be_true
36
+ end
35
37
 
36
- it "should generate a root URI based on the client settings" do
37
- @backend.root_uri.should be_kind_of(URI)
38
- @backend.root_uri.to_s.should == "http://127.0.0.1:8098"
38
+ it "should fail on any other code or error" do
39
+ @backend.should_receive(:get).and_raise("socket closed")
40
+ @backend.ping.should be_false
41
+ end
39
42
  end
40
43
 
41
- it "should compute a URI from a relative resource path" do
42
- @backend.path("baz").should be_kind_of(URI)
43
- @backend.path("foo").to_s.should == "http://127.0.0.1:8098/foo"
44
- @backend.path("foo", "bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
45
- @backend.path("/foo/bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
44
+ context "fetching an object" do
45
+ it "should perform a GET request and return an RObject" do
46
+ @backend.should_receive(:get).with([200,300], "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
47
+ @backend.fetch_object("foo", "db").should be_kind_of(Riak::RObject)
48
+ end
49
+
50
+ it "should pass the R quorum as a query parameter" do
51
+ @backend.should_receive(:get).with([200,300], "/riak/","foo", "db", {:r => 2}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
52
+ @backend.fetch_object("foo", "db", 2)
53
+ end
54
+
55
+ it "should escape the bucket and key names" do
56
+ @backend.should_receive(:get).with([200,300], "/riak/","foo%20", "%20bar", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
57
+ @backend.fetch_object('foo ',' bar').should be_kind_of(Riak::RObject)
58
+ end
46
59
  end
47
60
 
48
- it "should compute a URI from a relative resource path with a hash of query parameters" do
49
- @backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
61
+ context "reloading an object" do
62
+ before do
63
+ @object = Riak::RObject.new(@client.bucket("foo"), "bar")
64
+ end
65
+
66
+ it "should use conditional request headers" do
67
+ @object.etag = "etag"
68
+ @backend.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {'If-None-Match' => "etag"}).and_return({:code => 304})
69
+ @backend.reload_object(@object)
70
+ end
71
+
72
+ it "should return without modifying the object if the response is 304 Not Modified" do
73
+ @backend.should_receive(:get).and_return({:code => 304})
74
+ @backend.should_not_receive(:load_object)
75
+ @backend.reload_object(@object)
76
+ end
77
+
78
+ it "should raise an exception when the response code is not 200 or 304" do
79
+ @backend.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, ''))
80
+ lambda { @backend.reload_object(@object) }.should raise_error(Riak::FailedRequest)
81
+ end
82
+
83
+ it "should escape the bucket and key names" do
84
+ # @bucket.should_receive(:name).and_return("some/deep/path")
85
+ @object.bucket = @client.bucket("some/deep/path")
86
+ @object.key = "another/deep/path"
87
+ @backend.should_receive(:get).with([200,300,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
88
+ @backend.reload_object(@object)
89
+ end
50
90
  end
51
91
 
52
- it "should raise an error if a resource path is too short" do
53
- lambda { @backend.verify_path!(["/riak/"]) }.should raise_error(ArgumentError)
54
- lambda { @backend.verify_path!(["/riak/", "foo"]) }.should_not raise_error
55
- lambda { @backend.verify_path!(["/mapred"]) }.should_not raise_error
92
+ context "storing an object" do
93
+ before do
94
+ @bucket = Riak::Bucket.new(@client, "foo")
95
+ @object = Riak::RObject.new(@bucket)
96
+ @object.content_type = "text/plain"
97
+ @object.data = "This is some text."
98
+ @headers = @backend.store_headers(@object)
99
+ end
100
+
101
+ it "should use the raw_data as the request body" do
102
+ @object.content_type = "application/json"
103
+ body = @object.raw_data = "{this is probably invalid json!}}"
104
+ @backend.stub(:post).and_return({})
105
+ @object.should_not_receive(:serialize)
106
+ @backend.store_object(@object, false)
107
+ end
108
+
109
+ context "when the object has no key" do
110
+ it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
111
+ @backend.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})
112
+ @backend.store_object(@object, true, nil, nil)
113
+ @object.key.should == "somereallylongstring"
114
+ @object.vclock.should == "areallylonghashvalue"
115
+ end
116
+
117
+ it "should include persistence-tuning parameters in the query string" do
118
+ @backend.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})
119
+ @backend.store_object(@object, true, nil, 2)
120
+ end
121
+
122
+ it "should escape the bucket name" do
123
+ @object.bucket = @client.bucket("foo ")
124
+ @backend.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})
125
+ @backend.store_object(@object, true)
126
+ end
127
+ end
128
+
129
+ context "when the object has a key" do
130
+ before :each do
131
+ @object.key = "bar"
132
+ end
133
+
134
+ it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
135
+ @backend.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})
136
+ @backend.store_object(@object, true, nil, nil)
137
+ @object.key.should == "somereallylongstring"
138
+ @object.vclock.should == "areallylonghashvalue"
139
+ end
140
+
141
+ it "should include persistence-tuning parameters in the query string" do
142
+ @backend.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:w => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
143
+ @backend.store_object(@object, true, 2, nil)
144
+ end
145
+
146
+ it "should escape the bucket and key names" do
147
+ @backend.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})
148
+ @bucket.instance_variable_set(:@name, "foo ")
149
+ @object.key = "bar/baz"
150
+ @backend.store_object(@object, true, nil, nil)
151
+ end
152
+ end
56
153
  end
57
154
 
58
- describe "verify_path_and_body!" do
59
- it "should separate the path and body from given arguments" do
60
- uri, data = @backend.verify_path_and_body!(["/riak/", "foo", "This is the body."])
61
- uri.should == ["/riak/", "foo"]
62
- data.should == "This is the body."
155
+ context "deleting an object" do
156
+ it "should perform a DELETE request" do
157
+ @backend.should_receive(:delete).with([204,404], "/riak/", "foo", 'bar',{},{}).and_return(:code => 204)
158
+ @backend.delete_object("foo", "bar")
63
159
  end
64
160
 
65
- it "should raise an error if the body is not a string or IO" do
66
- lambda { @backend.verify_path_and_body!(["/riak/", "foo", nil]) }.should raise_error(ArgumentError)
67
- lambda { @backend.verify_path_and_body!(["/riak/", "foo", File.open("spec/fixtures/cat.jpg")]) }.should_not raise_error(ArgumentError)
161
+ it "should escape the bucket and key names" do
162
+ @backend.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
163
+ @backend.delete_object("bucket spaces", "deep/path")
68
164
  end
165
+ end
69
166
 
70
- it "should raise an error if a body is not given" do
71
- lambda { @backend.verify_path_and_body!(["/riak/", "foo"])}.should raise_error(ArgumentError)
167
+ context "fetching bucket properties" do
168
+ it "should GET the bucket URL and parse the response as JSON" do
169
+ @backend.should_receive(:get).with(200, "/riak/", "foo", {:keys => false, :props => true}, {}).and_return({:body => '{"props":{"n_val":3}}'})
170
+ @backend.get_bucket_props("foo").should == {"n_val" => 3}
72
171
  end
73
172
 
74
- it "should raise an error if a path is not given" do
75
- lambda { @backend.verify_path_and_body!(["/riak/"])}.should raise_error(ArgumentError)
173
+ it "should escape the bucket name" do
174
+ @backend.should_receive(:get).with(200, "/riak/", "foo%20bar", {:keys => false, :props => true}, {}).and_return({:body => '{"props":{"n_val":3}}'})
175
+ @backend.get_bucket_props("foo bar")
176
+ end
177
+ end
178
+
179
+ context "setting bucket properties" do
180
+ it "should PUT the properties to the bucket URL as JSON" do
181
+ @backend.should_receive(:put).with(204, "/riak/","foo", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
182
+ @backend.set_bucket_props("foo", {:n_val => 2})
183
+ end
184
+
185
+ it "should escape the bucket name" do
186
+ @backend.should_receive(:put).with(204, "/riak/","foo%20bar", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
187
+ @backend.set_bucket_props("foo bar", {:n_val => 2})
76
188
  end
77
189
  end
78
190
 
79
- describe "detecting valid response codes" do
80
- it "should accept strings or integers for either argument" do
81
- @backend.should be_valid_response("300", "300")
82
- @backend.should be_valid_response(300, "300")
83
- @backend.should be_valid_response("300", 300)
191
+ context "listing keys" do
192
+ it "should unescape key names" do
193
+ @backend.should_receive(:get).with(200, "/riak/","foo", {:props => false, :keys => true}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar%20baz"]}'})
194
+ @backend.list_keys("foo").should == ["bar baz"]
84
195
  end
85
196
 
86
- it "should accept an array of strings or integers for the expected code" do
87
- @backend.should be_valid_response([200,304], "200")
88
- @backend.should be_valid_response(["200",304], "200")
89
- @backend.should be_valid_response([200,"304"], "200")
90
- @backend.should be_valid_response(["200","304"], "200")
91
- @backend.should be_valid_response([200,304], 200)
197
+ it "should escape the bucket name" do
198
+ @backend.should_receive(:get).with(200, "/riak/","unescaped%20", {:props => false, :keys => true}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
199
+ @backend.list_keys("unescaped ").should == ["bar"]
92
200
  end
201
+ end
93
202
 
94
- it "should be false when none of the response codes match" do
95
- @backend.should_not be_valid_response(200, 404)
96
- @backend.should_not be_valid_response(["200","304"], 404)
97
- @backend.should_not be_valid_response([200,304], 404)
203
+ context "listing buckets" do
204
+ it "should GET the raw URL with ?buckets=true and parse the response as JSON" do
205
+ @backend.should_receive(:get).with(200, "/riak/", {:buckets => true}, {}).and_return({:body => '{"buckets":["foo", "bar", "baz"]}'})
206
+ @backend.list_buckets.should == ["foo", "bar", "baz"]
98
207
  end
99
208
  end
209
+
210
+ context "performing a MapReduce query" do
211
+ before do
212
+ @mr = Riak::MapReduce.new(@client).map("Riak.mapValues", :keep => true)
213
+ end
100
214
 
101
- describe "detecting whether a body should be returned" do
102
- it "should be false when the method is :head" do
103
- @backend.should_not be_return_body(:head, 200, false)
215
+ it "should issue POST request to the mapred endpoint" do
216
+ @backend.should_receive(:post).with(200, "/mapred", @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "[]"})
217
+ @backend.mapred(@mr)
104
218
  end
105
219
 
106
- it "should be false when the response code is 204, 205, or 304" do
107
- @backend.should_not be_return_body(:get, 204, false)
108
- @backend.should_not be_return_body(:get, 205, false)
109
- @backend.should_not be_return_body(:get, 304, false)
220
+ it "should vivify JSON responses" do
221
+ @backend.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '[{"key":"value"}]')
222
+ @backend.mapred(@mr).should == [{"key" => "value"}]
110
223
  end
111
224
 
112
- it "should be false when a streaming block was passed" do
113
- @backend.should_not be_return_body(:get, 200, true)
225
+ it "should return the full response hash for non-JSON responses" do
226
+ response = {:code => 200, :headers => {'content-type' => ["text/plain"]}, :body => 'This is some text.'}
227
+ @backend.stub!(:post).and_return(response)
228
+ @backend.mapred(@mr).should == response
114
229
  end
230
+ end
115
231
 
116
- it "should be true when the method is not head, a code other than 204, 205, or 304 was given, and there was no streaming block" do
117
- [:get, :put, :post, :delete].each do |method|
118
- [100,101,200,201,202,203,206,300,301,302,303,305,307,400,401,
119
- 402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,
120
- 500,501,502,503,504,505].each do |code|
121
- @backend.should be_return_body(method, code, false)
122
- @backend.should be_return_body(method, code.to_s, false)
123
- end
124
- end
232
+ context "getting statistics" do
233
+ it "should get the status URL and parse the response as JSON" do
234
+ @backend.should_receive(:get).with(200, "/stats", {}, {}).and_return({:body => '{"vnode_gets":20348}'})
235
+ @backend.stats.should == {"vnode_gets" => 20348}
125
236
  end
126
237
  end
127
238
 
128
- it "should force subclasses to implement the perform method" do
129
- lambda { @backend.send(:perform, :get, "/foo", {}, 200) }.should raise_error(NotImplementedError)
239
+ context "performing a link-walking query" do
240
+ before do
241
+ @bucket = Riak::Bucket.new(@client, "foo")
242
+ @object = Riak::RObject.new(@bucket, "bar")
243
+ @body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
244
+ @specs = [Riak::WalkSpec.new(:tag => "next", :keep => true)]
245
+ end
246
+
247
+ it "should perform a GET request for the given object and walk specs" do
248
+ @backend.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")
249
+ @backend.link_walk(@object, @specs)
250
+ end
251
+
252
+ it "should parse the results into arrays of objects" do
253
+ @backend.should_receive(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
254
+ results = @backend.link_walk(@object, @specs)
255
+ results.should be_kind_of(Array)
256
+ results.first.should be_kind_of(Array)
257
+ obj = results.first.first
258
+ obj.should be_kind_of(Riak::RObject)
259
+ obj.content_type.should == "text/plain"
260
+ obj.key.should == "baz"
261
+ obj.bucket.should == @bucket
262
+ end
263
+
264
+ it "should assign the bucket for newly parsed objects" do
265
+ @backend.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
266
+ @client.should_receive(:bucket).with("foo").and_return(@bucket)
267
+ @backend.link_walk(@object, @specs)
268
+ end
130
269
  end
131
270
  end