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.
- data/Rakefile +10 -3
- data/lib/riak/bucket.rb +31 -46
- data/lib/riak/client.rb +18 -4
- data/lib/riak/client/excon_backend.rb +15 -1
- data/lib/riak/client/http_backend.rb +169 -196
- data/lib/riak/client/http_backend/configuration.rb +56 -0
- data/lib/riak/client/http_backend/object_methods.rb +101 -0
- data/lib/riak/client/http_backend/request_headers.rb +46 -0
- data/lib/riak/client/http_backend/transport_methods.rb +208 -0
- data/lib/riak/core_ext/blank.rb +14 -2
- data/lib/riak/link.rb +9 -4
- data/lib/riak/locale/en.yml +2 -0
- data/lib/riak/map_reduce.rb +37 -103
- data/lib/riak/map_reduce/filter_builder.rb +106 -0
- data/lib/riak/map_reduce/phase.rb +108 -0
- data/lib/riak/robject.rb +19 -96
- data/lib/riak/util/escape.rb +7 -0
- data/riak-client.gemspec +5 -5
- data/spec/riak/bucket_spec.rb +57 -111
- data/spec/riak/client_spec.rb +97 -78
- data/spec/riak/escape_spec.rb +7 -0
- data/spec/riak/http_backend/object_methods_spec.rb +218 -0
- data/spec/riak/http_backend_spec.rb +204 -65
- data/spec/riak/link_spec.rb +8 -0
- data/spec/riak/map_reduce_spec.rb +26 -151
- data/spec/riak/object_spec.rb +36 -350
- data/spec/riak/search_spec.rb +5 -16
- data/spec/spec_helper.rb +1 -1
- metadata +29 -8
data/spec/riak/escape_spec.rb
CHANGED
@@ -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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
59
|
-
it "should
|
60
|
-
|
61
|
-
|
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
|
66
|
-
|
67
|
-
|
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
|
-
|
71
|
-
|
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
|
75
|
-
|
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
|
-
|
80
|
-
it "should
|
81
|
-
@backend.
|
82
|
-
@backend.should
|
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
|
87
|
-
@backend.
|
88
|
-
@backend.should
|
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
|
-
|
95
|
-
|
96
|
-
@backend.
|
97
|
-
@backend.
|
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
|
-
|
102
|
-
|
103
|
-
@backend.
|
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
|
107
|
-
@backend.
|
108
|
-
@backend.
|
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
|
113
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
129
|
-
|
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
|