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.
@@ -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