riak-client 0.9.8 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +17 -11
  3. data/Guardfile +14 -0
  4. data/Rakefile +18 -44
  5. data/erl_src/riak_kv_test_backend.beam +0 -0
  6. data/erl_src/riak_kv_test_backend.erl +461 -128
  7. data/erl_src/riak_search_test_backend.beam +0 -0
  8. data/erl_src/riak_search_test_backend.erl +175 -0
  9. data/lib/active_support/cache/riak_store.rb +0 -13
  10. data/lib/riak.rb +11 -16
  11. data/lib/riak/bucket.rb +59 -41
  12. data/lib/riak/cache_store.rb +1 -14
  13. data/lib/riak/client.rb +145 -73
  14. data/lib/riak/client/beefcake/messages.rb +36 -31
  15. data/lib/riak/client/beefcake/object_methods.rb +27 -19
  16. data/lib/riak/client/beefcake_protobuffs_backend.rb +27 -33
  17. data/lib/riak/client/excon_backend.rb +0 -13
  18. data/lib/riak/client/http_backend.rb +95 -60
  19. data/lib/riak/client/http_backend/configuration.rb +144 -19
  20. data/lib/riak/client/http_backend/key_streamer.rb +1 -14
  21. data/lib/riak/client/http_backend/object_methods.rb +16 -16
  22. data/lib/riak/client/http_backend/request_headers.rb +0 -13
  23. data/lib/riak/client/http_backend/transport_methods.rb +26 -56
  24. data/lib/riak/client/net_http_backend.rb +11 -13
  25. data/lib/riak/client/protobuffs_backend.rb +21 -19
  26. data/lib/riak/client/pump.rb +1 -15
  27. data/lib/riak/client/search.rb +85 -0
  28. data/lib/riak/cluster.rb +151 -0
  29. data/lib/riak/core_ext.rb +1 -0
  30. data/lib/riak/core_ext/deep_dup.rb +13 -0
  31. data/lib/riak/core_ext/json.rb +15 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +1 -1
  33. data/lib/riak/core_ext/symbolize_keys.rb +1 -1
  34. data/lib/riak/encoding.rb +6 -0
  35. data/lib/riak/failed_request.rb +2 -15
  36. data/lib/riak/i18n.rb +0 -13
  37. data/lib/riak/json.rb +19 -8
  38. data/lib/riak/link.rb +18 -20
  39. data/lib/riak/locale/en.yml +13 -16
  40. data/lib/riak/map_reduce.rb +40 -20
  41. data/lib/riak/map_reduce/filter_builder.rb +14 -18
  42. data/lib/riak/map_reduce/phase.rb +0 -13
  43. data/lib/riak/map_reduce_error.rb +0 -13
  44. data/lib/riak/node.rb +38 -0
  45. data/lib/riak/node/configuration.rb +286 -0
  46. data/lib/riak/node/console.rb +139 -0
  47. data/lib/riak/node/control.rb +207 -0
  48. data/lib/riak/node/defaults.rb +70 -0
  49. data/lib/riak/node/generation.rb +99 -0
  50. data/lib/riak/node/log.rb +34 -0
  51. data/lib/riak/node/version.rb +37 -0
  52. data/lib/riak/robject.rb +45 -41
  53. data/lib/riak/search.rb +2 -161
  54. data/lib/riak/serializers.rb +74 -0
  55. data/lib/riak/stamp.rb +77 -0
  56. data/lib/riak/test_server.rb +56 -220
  57. data/lib/riak/util/escape.rb +58 -17
  58. data/lib/riak/util/headers.rb +2 -15
  59. data/lib/riak/util/multipart.rb +0 -13
  60. data/lib/riak/util/multipart/stream_parser.rb +0 -13
  61. data/lib/riak/util/tcp_socket_extensions.rb +1 -14
  62. data/lib/riak/util/translation.rb +0 -13
  63. data/lib/riak/version.rb +3 -0
  64. data/lib/riak/walk_spec.rb +0 -13
  65. data/riak-client.gemspec +27 -47
  66. data/spec/fixtures/multipart-with-marked-tombstones.txt +17 -0
  67. data/spec/fixtures/multipart-with-unmarked-tombstone.txt +16 -0
  68. data/spec/integration/riak/cache_store_spec.rb +2 -40
  69. data/spec/integration/riak/cluster_spec.rb +88 -0
  70. data/spec/integration/riak/http_backends_spec.rb +6 -30
  71. data/spec/integration/riak/node_spec.rb +184 -0
  72. data/spec/integration/riak/protobuffs_backends_spec.rb +2 -26
  73. data/spec/integration/riak/test_server_spec.rb +31 -167
  74. data/spec/riak/beefcake_protobuffs_backend_spec.rb +5 -4
  75. data/spec/riak/bucket_spec.rb +26 -36
  76. data/spec/riak/client_spec.rb +44 -38
  77. data/spec/riak/escape_spec.rb +56 -30
  78. data/spec/riak/excon_backend_spec.rb +4 -17
  79. data/spec/riak/headers_spec.rb +1 -14
  80. data/spec/riak/http_backend/configuration_spec.rb +211 -34
  81. data/spec/riak/http_backend/object_methods_spec.rb +52 -18
  82. data/spec/riak/http_backend/transport_methods_spec.rb +5 -38
  83. data/spec/riak/http_backend_spec.rb +84 -78
  84. data/spec/riak/link_spec.rb +19 -18
  85. data/spec/riak/map_reduce/filter_builder_spec.rb +1 -14
  86. data/spec/riak/map_reduce/phase_spec.rb +1 -14
  87. data/spec/riak/map_reduce_spec.rb +141 -43
  88. data/spec/riak/multipart_spec.rb +1 -14
  89. data/spec/riak/net_http_backend_spec.rb +2 -15
  90. data/spec/riak/robject_spec.rb +129 -97
  91. data/spec/riak/search_spec.rb +45 -62
  92. data/spec/riak/serializers_spec.rb +93 -0
  93. data/spec/riak/stamp_spec.rb +54 -0
  94. data/spec/riak/stream_parser_spec.rb +3 -16
  95. data/spec/riak/walk_spec_spec.rb +1 -14
  96. data/spec/spec_helper.rb +22 -27
  97. data/spec/support/http_backend_implementation_examples.rb +49 -79
  98. data/spec/support/integration_setup.rb +10 -0
  99. data/spec/support/mock_server.rb +0 -14
  100. data/spec/support/mocks.rb +0 -13
  101. data/spec/support/test_server.rb +30 -0
  102. data/spec/support/test_server.yml.example +14 -2
  103. data/spec/support/unified_backend_examples.rb +36 -27
  104. metadata +100 -31
  105. data/lib/riak/client/curb_backend.rb +0 -89
  106. data/spec/riak/curb_backend_spec.rb +0 -76
@@ -1,23 +1,12 @@
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__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::Client::HTTPBackend::ObjectMethods do
17
4
  before :each do
18
5
  @client = Riak::Client.new
19
6
  @backend = Riak::Client::HTTPBackend.new(@client)
7
+ @bucket = Riak::Bucket.new(@client, "bucket")
20
8
  @object = Riak::RObject.new(@bucket, "bar")
9
+ @backend.stub!(:new_scheme?).and_return(false)
21
10
  end
22
11
 
23
12
  describe "loading object data from the response" do
@@ -78,6 +67,12 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
78
67
  @object.meta["some-kind-of-robot"].should == ["for AWESOME"]
79
68
  end
80
69
 
70
+ it "should load indexes from the headers" do
71
+ @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "x-riak-index-email_bin" => ["sean@basho.com"], "x-riak-index-rank_int" => ["50"]}, :body => "{}"})
72
+ @object.indexes['email_bin'].should include('sean@basho.com')
73
+ @object.indexes['rank_int'].should include(50)
74
+ end
75
+
81
76
  it "should parse the location header into the key when present" do
82
77
  @backend.load_object(@object, {:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
83
78
  @object.key.should == "baz"
@@ -88,9 +83,20 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
88
83
  @object.key.should == "[baz]"
89
84
  end
90
85
 
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
86
+ context "when the response code is 300 and the content-type is multipart/mixed" do
87
+ let(:http_response) { {:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 } }
88
+ let(:other_object) { Riak::RObject.new(@bucket, "bar2") }
89
+
90
+ it 'marks the object as in conflict' do
91
+ @backend.load_object(@object, http_response)
92
+ @object.should be_conflict
93
+ end
94
+
95
+ it 'attempts to resolve the conflict' do
96
+ @object.should respond_to(:attempt_conflict_resolution)
97
+ @object.should_receive(:attempt_conflict_resolution).and_return(other_object)
98
+ @backend.load_object(@object, http_response).should be(other_object)
99
+ end
94
100
  end
95
101
 
96
102
  it "should unescape the key given in the location header" do
@@ -120,7 +126,7 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
120
126
  end
121
127
  end
122
128
 
123
- describe "headers used for storing the object" do
129
+ describe "headers used for storing the object" do
124
130
  it "should include the content type" do
125
131
  @object.content_type = "application/json"
126
132
  @backend.store_headers(@object)["Content-Type"].should == "application/json"
@@ -168,6 +174,15 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
168
174
  @backend.store_headers(@object).should have_key("Link")
169
175
  @backend.store_headers(@object)["Link"].should_not include('riaktag="up"')
170
176
  end
177
+
178
+ context "when using the new URL scheme" do
179
+ before { @backend.stub!(:new_scheme?).and_return(true) }
180
+
181
+ it "should encode Links using the new format" do
182
+ @backend.store_headers(@object).should have_key("Link")
183
+ @backend.store_headers(@object)['Link'].should include('</buckets/foo/keys/baz>; riaktag="next"')
184
+ end
185
+ end
171
186
  end
172
187
 
173
188
  it "should exclude the Link header when no links are present" do
@@ -195,6 +210,25 @@ describe Riak::Client::HTTPBackend::ObjectMethods do
195
210
  @backend.store_headers(@object)["X-Riak-Meta-powers"].should == "for awesome"
196
211
  end
197
212
  end
213
+
214
+ describe "when indexes are present" do
215
+ before :each do
216
+ @object.indexes = {"email_bin" => Set.new(['sean@basho.com', 'seancribbs@gmail.com']), "rank_int" => Set.new([50])}
217
+ end
218
+
219
+ it "should include X-Riak-Index-* headers for each index key" do
220
+ @backend.store_headers(@object).should have_key('X-Riak-Index-email_bin')
221
+ @backend.store_headers(@object).should have_key('X-Riak-Index-rank_int')
222
+ end
223
+
224
+ it "should join multi-valued indexes into a single header" do
225
+ @backend.store_headers(@object)['X-Riak-Index-email_bin'].should == 'sean@basho.com, seancribbs@gmail.com'
226
+ end
227
+
228
+ it "should turn integer indexes into strings in the header" do
229
+ @backend.store_headers(@object)['X-Riak-Index-rank_int'].should == '50'
230
+ end
231
+ end
198
232
  end
199
233
 
200
234
  describe "headers used for reloading the object" do
@@ -1,17 +1,4 @@
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__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::Client::HTTPBackend::TransportMethods do
17
4
  before :each do
@@ -41,31 +28,11 @@ describe Riak::Client::HTTPBackend::TransportMethods do
41
28
  @backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
42
29
  end
43
30
 
44
- it "should raise an error if a resource path is too short" do
45
- lambda { @backend.verify_path!(["/riak/"]) }.should raise_error(ArgumentError)
46
- lambda { @backend.verify_path!(["/riak/", "foo"]) }.should_not raise_error
47
- lambda { @backend.verify_path!(["/mapred"]) }.should_not raise_error
48
- end
49
-
50
- describe "verify_path_and_body!" do
51
- it "should separate the path and body from given arguments" do
52
- uri, data = @backend.verify_path_and_body!(["/riak/", "foo", "This is the body."])
53
- uri.should == ["/riak/", "foo"]
54
- data.should == "This is the body."
55
- end
56
-
31
+ describe "verify_body!" do
57
32
  it "should raise an error if the body is not a string or IO or IO-like (responds to :read)" do
58
- lambda { @backend.verify_path_and_body!(["/riak/", "foo", nil]) }.should raise_error(ArgumentError)
59
- lambda { @backend.verify_path_and_body!(["/riak/", "foo", File.open("spec/fixtures/cat.jpg")]) }.should_not raise_error(ArgumentError)
60
- lambda { @backend.verify_path_and_body!(["/riak/", "foo", Tempfile.new('riak-spec')]) }.should_not raise_error(ArgumentError)
61
- end
62
-
63
- it "should raise an error if a body is not given" do
64
- lambda { @backend.verify_path_and_body!(["/riak/", "foo"])}.should raise_error(ArgumentError)
65
- end
66
-
67
- it "should raise an error if a path is not given" do
68
- lambda { @backend.verify_path_and_body!(["/riak/"])}.should raise_error(ArgumentError)
33
+ lambda { @backend.verify_body!(nil) }.should raise_error(ArgumentError)
34
+ lambda { @backend.verify_body!(File.open("spec/fixtures/cat.jpg")) }.should_not raise_error(ArgumentError)
35
+ lambda { @backend.verify_body!(Tempfile.new('riak-spec')) }.should_not raise_error(ArgumentError)
69
36
  end
70
37
  end
71
38
 
@@ -1,17 +1,4 @@
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__))
1
+ require 'spec_helper'
15
2
 
16
3
  describe Riak::Client::HTTPBackend do
17
4
  before :each do
@@ -31,7 +18,7 @@ describe Riak::Client::HTTPBackend do
31
18
 
32
19
  context "pinging the server" do
33
20
  it "should succeed on 200" do
34
- @backend.should_receive(:get).with(200, "/ping", {}, {}).and_return({:code => 200, :body => "OK"})
21
+ @backend.should_receive(:get).with(200, @backend.ping_path).and_return({:code => 200, :body => "OK"})
35
22
  @backend.ping.should be_true
36
23
  end
37
24
 
@@ -43,17 +30,17 @@ describe Riak::Client::HTTPBackend do
43
30
 
44
31
  context "fetching an object" do
45
32
  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"}'})
33
+ @backend.should_receive(:get).with([200,300], @backend.object_path('foo', 'db')).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
47
34
  @backend.fetch_object("foo", "db").should be_kind_of(Riak::RObject)
48
35
  end
49
36
 
50
37
  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)
38
+ @backend.should_receive(:get).with([200,300], @backend.object_path("foo", "db", {:r => 2})).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
39
+ @backend.fetch_object("foo", "db", :r => 2)
53
40
  end
54
41
 
55
42
  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"}'})
43
+ @backend.should_receive(:get).with([200,300], @backend.object_path('foo ', ' bar')).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
57
44
  @backend.fetch_object('foo ',' bar').should be_kind_of(Riak::RObject)
58
45
  end
59
46
  end
@@ -65,7 +52,7 @@ describe Riak::Client::HTTPBackend do
65
52
 
66
53
  it "should use conditional request headers" do
67
54
  @object.etag = "etag"
68
- @backend.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {'If-None-Match' => "etag"}).and_return({:code => 304})
55
+ @backend.should_receive(:get).with([200,300,304], @backend.object_path('foo', 'bar'), {'If-None-Match' => "etag"}).and_return({:code => 304})
69
56
  @backend.reload_object(@object)
70
57
  end
71
58
 
@@ -84,7 +71,7 @@ describe Riak::Client::HTTPBackend do
84
71
  # @bucket.should_receive(:name).and_return("some/deep/path")
85
72
  @object.bucket = @client.bucket("some/deep/path")
86
73
  @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})
74
+ @backend.should_receive(:get).with([200,300,304], @backend.object_path(@object.bucket.name, @object.key), {}).and_return({:code => 304})
88
75
  @backend.reload_object(@object)
89
76
  end
90
77
  end
@@ -103,26 +90,20 @@ describe Riak::Client::HTTPBackend do
103
90
  body = @object.raw_data = "{this is probably invalid json!}}"
104
91
  @backend.stub(:post).and_return({})
105
92
  @object.should_not_receive(:serialize)
106
- @backend.store_object(@object, false)
93
+ @backend.store_object(@object, :returnbody => false)
107
94
  end
108
-
95
+
109
96
  context "when the object has no key" do
110
97
  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)
98
+ @backend.should_receive(:post).with(201, @backend.object_path("foo", nil, {:returnbody => true}), "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
99
+ @backend.store_object(@object, :returnbody => true)
113
100
  @object.key.should == "somereallylongstring"
114
101
  @object.vclock.should == "areallylonghashvalue"
115
102
  end
116
103
 
117
104
  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)
105
+ @backend.should_receive(:post).with(201, @backend.object_path("foo", nil, {:dw => 2, :returnbody => true}), "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
106
+ @backend.store_object(@object, :returnbody => true, :dw => 2)
126
107
  end
127
108
  end
128
109
 
@@ -132,88 +113,71 @@ describe Riak::Client::HTTPBackend do
132
113
  end
133
114
 
134
115
  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)
116
+ @backend.should_receive(:put).with([200,204,300], @backend.object_path("foo", "bar", {:returnbody => true}), "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
117
+ @backend.store_object(@object, :returnbody => true)
137
118
  @object.key.should == "somereallylongstring"
138
119
  @object.vclock.should == "areallylonghashvalue"
139
120
  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
121
 
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)
122
+ it "should include persistence-tuning parameters in the query string" do
123
+ @backend.should_receive(:put).with([200,204,300], @backend.object_path("foo", "bar", {:w => 2, :returnbody => true}), "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
124
+ @backend.store_object(@object, :returnbody => true, :w => 2)
151
125
  end
152
126
  end
153
127
  end
154
128
 
155
129
  context "deleting an object" do
156
130
  it "should perform a DELETE request" do
157
- @backend.should_receive(:delete).with([204,404], "/riak/", "foo", 'bar',{},{}).and_return(:code => 204)
131
+ @backend.should_receive(:delete).with([204,404], @backend.object_path("foo", 'bar'), {}).and_return(:code => 204)
158
132
  @backend.delete_object("foo", "bar")
159
133
  end
160
134
 
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")
135
+ it "should perform a DELETE request with the provided vclock" do
136
+ @backend.should_receive(:delete).with([204,404], @backend.object_path("foo", 'bar'), {'X-Riak-VClock' => "myvclock"}).and_return(:code => 204)
137
+ @backend.delete_object('foo', 'bar', :vclock => "myvclock")
138
+ end
139
+
140
+ it "should perform a DELETE request with the requested quorum value" do
141
+ @backend.should_receive(:delete).with([204,404], @backend.object_path("foo", 'bar', {:rw => 2}), {'X-Riak-VClock' => "myvclock"}).and_return(:code => 204)
142
+ @backend.delete_object('foo', 'bar', :vclock => "myvclock", :rw => 2)
164
143
  end
165
144
  end
166
145
 
167
146
  context "fetching bucket properties" do
168
147
  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}}'})
148
+ @backend.should_receive(:get).with(200, @backend.bucket_properties_path('foo')).and_return({:body => '{"props":{"n_val":3}}'})
170
149
  @backend.get_bucket_props("foo").should == {"n_val" => 3}
171
150
  end
172
-
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
151
  end
178
-
152
+
179
153
  context "setting bucket properties" do
180
154
  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})
155
+ @backend.should_receive(:put).with(204, @backend.bucket_properties_path('foo'), '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
156
+ @backend.set_bucket_props("foo", {:n_val => 2})
188
157
  end
189
158
  end
190
-
159
+
191
160
  context "listing keys" do
192
161
  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"]}'})
162
+ @backend.should_receive(:get).with(200, @backend.key_list_path('foo')).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar%20baz"]}'})
194
163
  @backend.list_keys("foo").should == ["bar baz"]
195
164
  end
196
-
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"]
200
- end
201
165
  end
202
166
 
203
167
  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"]}'})
168
+ it "should GET the bucket list URL and parse the response as JSON" do
169
+ @backend.should_receive(:get).with(200, @backend.bucket_list_path).and_return({:body => '{"buckets":["foo", "bar", "baz"]}'})
206
170
  @backend.list_buckets.should == ["foo", "bar", "baz"]
207
171
  end
208
172
  end
209
-
173
+
210
174
  context "performing a MapReduce query" do
211
175
  before do
212
176
  @mr = Riak::MapReduce.new(@client).map("Riak.mapValues", :keep => true)
213
177
  end
214
178
 
215
179
  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 => "[]"})
180
+ @backend.should_receive(:post).with(200, @backend.mapred_path, @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "[]"})
217
181
  @backend.mapred(@mr)
218
182
  end
219
183
 
@@ -230,7 +194,7 @@ describe Riak::Client::HTTPBackend do
230
194
 
231
195
  it "should stream results through the block" do
232
196
  data = File.read("spec/fixtures/multipart-mapreduce.txt")
233
- @backend.should_receive(:post).with(200, "/mapred", {:chunked => true}, @mr.to_json, hash_including("Content-Type" => "application/json")).and_yield(data)
197
+ @backend.should_receive(:post).with(200, @backend.mapred_path(:chunked => true), @mr.to_json, hash_including("Content-Type" => "application/json")).and_yield(data)
234
198
  block = mock
235
199
  block.should_receive(:ping).twice.and_return(true)
236
200
  @backend.mapred(@mr) do |phase, data|
@@ -243,11 +207,11 @@ describe Riak::Client::HTTPBackend do
243
207
 
244
208
  context "getting statistics" do
245
209
  it "should get the status URL and parse the response as JSON" do
246
- @backend.should_receive(:get).with(200, "/stats", {}, {}).and_return({:body => '{"vnode_gets":20348}'})
210
+ @backend.should_receive(:get).with(200, @backend.stats_path).and_return({:body => '{"vnode_gets":20348}'})
247
211
  @backend.stats.should == {"vnode_gets" => 20348}
248
212
  end
249
213
  end
250
-
214
+
251
215
  context "performing a link-walking query" do
252
216
  before do
253
217
  @bucket = Riak::Bucket.new(@client, "foo")
@@ -257,7 +221,7 @@ describe Riak::Client::HTTPBackend do
257
221
  end
258
222
 
259
223
  it "should perform a GET request for the given object and walk specs" do
260
- @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")
224
+ @backend.should_receive(:get).with(200, @backend.link_walk_path(@bucket.name, @object.key, @specs)).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")
261
225
  @backend.link_walk(@object, @specs)
262
226
  end
263
227
 
@@ -278,5 +242,47 @@ describe Riak::Client::HTTPBackend do
278
242
  @client.should_receive(:bucket).with("foo").and_return(@bucket)
279
243
  @backend.link_walk(@object, @specs)
280
244
  end
245
+
246
+ it "should discard unmarked tombstones" do
247
+ @backend.should_receive(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=CvfrSTCWwIiwezy0Zt1B2zwKgS7"]}, :body => File.read(File.expand_path("../../fixtures/multipart-with-unmarked-tombstone.txt", __FILE__)))
248
+ results = @backend.link_walk(@object, @specs)
249
+ results.first.should be_empty
250
+ end
251
+
252
+ it "should discard marked tombstones" do
253
+ @backend.should_receive(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=ADqgQtdmA5iQgyR5UGzX6V3HZtI"]}, :body => File.read(File.expand_path("../../fixtures/multipart-with-marked-tombstones.txt", __FILE__)))
254
+ results = @backend.link_walk(@object, @specs)
255
+ results.first.should be_empty
256
+ end
257
+ end
258
+
259
+ context "performing a search" do
260
+ before { @backend.send(:server_config)[:riak_solr_searcher_wm] = '/solr' }
261
+
262
+ it "should request the searcher resource" do
263
+ @backend.should_receive(:get).
264
+ with(200, @backend.solr_select_path(nil, 'foo', {'wt' => 'json'})).
265
+ and_return(:code => 200, :headers => {"content-type" => ['application/json']}, :body => '{}')
266
+ @backend.search(nil, 'foo')
267
+ end
268
+
269
+ it "should vivify JSON responses" do
270
+ @backend.should_receive(:get).and_return({:code => 200, :headers => {"content-type"=>["application/json"]}, :body => '{"response":{"docs":["foo"]}}'})
271
+ @backend.search(nil, "foo").should == {"response" => {"docs" => ["foo"]}}
272
+ end
273
+
274
+ it "should return non-JSON responses raw" do
275
+ @backend.should_receive(:get).and_return({:code => 200, :headers => {"content-type"=>["text/plain"]}, :body => '{"response":{"docs":["foo"]}}'})
276
+ @backend.search(nil, "foo").should == '{"response":{"docs":["foo"]}}'
277
+ end
278
+ end
279
+
280
+ context "updating a search index" do
281
+ before { @backend.send(:server_config)[:riak_solr_indexer_wm] = '/solr' }
282
+
283
+ it "should request the indexer resource" do
284
+ @backend.should_receive(:post).with(200, @backend.solr_update_path(nil), 'postbody', {"Content-Type" => "text/xml"})
285
+ @backend.update_search_index(nil, 'postbody')
286
+ end
281
287
  end
282
288
  end