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