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/link_spec.rb
CHANGED
@@ -32,11 +32,19 @@ describe Riak::Link do
|
|
32
32
|
result[1].key.should == nil
|
33
33
|
result[1].rel.should == "up"
|
34
34
|
end
|
35
|
+
|
36
|
+
it "should keep the url intact if it does not point to a bucket or bucket/key" do
|
37
|
+
result = Riak::Link.parse('</mapred>; rel="riak_kv_wm_mapred"')
|
38
|
+
result[0].url.should == "/mapred"
|
39
|
+
result[0].bucket.should be_nil
|
40
|
+
result[0].key.should be_nil
|
41
|
+
end
|
35
42
|
end
|
36
43
|
|
37
44
|
it "should convert to a string appropriate for use in the Link header" do
|
38
45
|
Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
|
39
46
|
Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
|
47
|
+
Riak::Link.new("/riak", "riak_kv_wm_raw").to_s.should == '</riak>; riaktag="riak_kv_wm_raw"'
|
40
48
|
end
|
41
49
|
|
42
50
|
it "should convert to a walk spec when pointing to an object" do
|
@@ -16,8 +16,8 @@ require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
|
16
16
|
describe Riak::MapReduce do
|
17
17
|
before :each do
|
18
18
|
@client = Riak::Client.new
|
19
|
-
@
|
20
|
-
@client.stub!(:
|
19
|
+
@backend = mock("Backend")
|
20
|
+
@client.stub!(:backend).and_return(@backend)
|
21
21
|
@mr = Riak::MapReduce.new(@client)
|
22
22
|
end
|
23
23
|
|
@@ -101,6 +101,20 @@ describe Riak::MapReduce do
|
|
101
101
|
@mr.add("docs")
|
102
102
|
@mr.inputs.should == "docs"
|
103
103
|
end
|
104
|
+
|
105
|
+
it "should accept a list of key-filters along with a bucket" do
|
106
|
+
@mr.add("foo", [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]])
|
107
|
+
@mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should add a bucket and filter list via a builder block" do
|
111
|
+
@mr.filter("foo") do
|
112
|
+
tokenize "-", 3
|
113
|
+
string_to_int
|
114
|
+
between 2009, 2010
|
115
|
+
end
|
116
|
+
@mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
|
117
|
+
end
|
104
118
|
end
|
105
119
|
|
106
120
|
[:map, :reduce].each do |type|
|
@@ -223,24 +237,20 @@ describe Riak::MapReduce do
|
|
223
237
|
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
224
238
|
end
|
225
239
|
|
226
|
-
it "should
|
227
|
-
@
|
228
|
-
@mr.run
|
229
|
-
end
|
230
|
-
|
231
|
-
it "should vivify JSON responses" do
|
232
|
-
@http.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '[{"key":"value"}]')
|
233
|
-
@mr.run.should == [{"key" => "value"}]
|
240
|
+
it "should submit the query to the backend" do
|
241
|
+
@backend.should_receive(:mapred).with(@mr).and_return([])
|
242
|
+
@mr.run.should == []
|
234
243
|
end
|
235
244
|
|
236
|
-
it "should
|
237
|
-
|
238
|
-
@
|
239
|
-
@mr.run
|
245
|
+
it "should pass the given block to the backend for streaming" do
|
246
|
+
arr = []
|
247
|
+
@backend.should_receive(:mapred).with(@mr).and_yield("foo").and_yield("bar")
|
248
|
+
@mr.run {|v| arr << v }
|
249
|
+
arr.should == ["foo", "bar"]
|
240
250
|
end
|
241
251
|
|
242
252
|
it "should interpret failed requests with JSON content-types as map reduce errors" do
|
243
|
-
@
|
253
|
+
@backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
|
244
254
|
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
245
255
|
begin
|
246
256
|
@mr.run
|
@@ -252,143 +262,8 @@ describe Riak::MapReduce do
|
|
252
262
|
end
|
253
263
|
|
254
264
|
it "should re-raise non-JSON error responses" do
|
255
|
-
@
|
265
|
+
@backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
|
256
266
|
lambda { @mr.run }.should raise_error(Riak::FailedRequest)
|
257
267
|
end
|
258
268
|
end
|
259
269
|
end
|
260
|
-
|
261
|
-
describe Riak::MapReduce::Phase do
|
262
|
-
before :each do
|
263
|
-
@fun = "function(v,_,_){ return v['values'][0]['data']; }"
|
264
|
-
end
|
265
|
-
|
266
|
-
it "should initialize with a type and a function" do
|
267
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun, :language => "javascript")
|
268
|
-
phase.type.should == :map
|
269
|
-
phase.function.should == @fun
|
270
|
-
phase.language.should == "javascript"
|
271
|
-
end
|
272
|
-
|
273
|
-
it "should initialize with a type and an MF" do
|
274
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"], :language => "erlang")
|
275
|
-
phase.type.should == :map
|
276
|
-
phase.function.should == ["module", "function"]
|
277
|
-
phase.language.should == "erlang"
|
278
|
-
end
|
279
|
-
|
280
|
-
it "should initialize with a type and a bucket/key" do
|
281
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "funs", :key => "awesome_map"}, :language => "javascript")
|
282
|
-
phase.type.should == :map
|
283
|
-
phase.function.should == {:bucket => "funs", :key => "awesome_map"}
|
284
|
-
phase.language.should == "javascript"
|
285
|
-
end
|
286
|
-
|
287
|
-
it "should assume the language is erlang when the function is an array" do
|
288
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"])
|
289
|
-
phase.language.should == "erlang"
|
290
|
-
end
|
291
|
-
|
292
|
-
it "should assume the language is javascript when the function is a string" do
|
293
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun)
|
294
|
-
phase.language.should == "javascript"
|
295
|
-
end
|
296
|
-
|
297
|
-
it "should assume the language is javascript when the function is a hash" do
|
298
|
-
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "jobs", :key => "awesome_map"})
|
299
|
-
phase.language.should == "javascript"
|
300
|
-
end
|
301
|
-
|
302
|
-
it "should accept a WalkSpec for the function when a link phase" do
|
303
|
-
phase = Riak::MapReduce::Phase.new(:type => :link, :function => Riak::WalkSpec.new({}))
|
304
|
-
phase.function.should be_kind_of(Riak::WalkSpec)
|
305
|
-
end
|
306
|
-
|
307
|
-
it "should raise an error if a WalkSpec is given for a phase type other than :link" do
|
308
|
-
lambda { Riak::MapReduce::Phase.new(:type => :map, :function => Riak::WalkSpec.new({})) }.should raise_error(ArgumentError)
|
309
|
-
end
|
310
|
-
|
311
|
-
describe "converting to JSON for the job" do
|
312
|
-
before :each do
|
313
|
-
@phase = Riak::MapReduce::Phase.new(:type => :map, :function => "")
|
314
|
-
end
|
315
|
-
|
316
|
-
[:map, :reduce].each do |type|
|
317
|
-
describe "when a #{type} phase" do
|
318
|
-
before :each do
|
319
|
-
@phase.type = type
|
320
|
-
end
|
321
|
-
|
322
|
-
it "should be an object with a single key of '#{type}'" do
|
323
|
-
@phase.to_json.should =~ /^\{"#{type}":/
|
324
|
-
end
|
325
|
-
|
326
|
-
it "should include the language" do
|
327
|
-
@phase.to_json.should =~ /"language":/
|
328
|
-
end
|
329
|
-
|
330
|
-
it "should include the keep value" do
|
331
|
-
@phase.to_json.should =~ /"keep":false/
|
332
|
-
@phase.keep = true
|
333
|
-
@phase.to_json.should =~ /"keep":true/
|
334
|
-
end
|
335
|
-
|
336
|
-
it "should include the function source when the function is a source string" do
|
337
|
-
@phase.function = "function(v,_,_){ return v; }"
|
338
|
-
@phase.to_json.should include(@phase.function)
|
339
|
-
@phase.to_json.should =~ /"source":/
|
340
|
-
end
|
341
|
-
|
342
|
-
it "should include the function name when the function is not a lambda" do
|
343
|
-
@phase.function = "Riak.mapValues"
|
344
|
-
@phase.to_json.should include('"name":"Riak.mapValues"')
|
345
|
-
@phase.to_json.should_not include('"source"')
|
346
|
-
end
|
347
|
-
|
348
|
-
it "should include the bucket and key when referring to a stored function" do
|
349
|
-
@phase.function = {:bucket => "design", :key => "wordcount_map"}
|
350
|
-
@phase.to_json.should include('"bucket":"design"')
|
351
|
-
@phase.to_json.should include('"key":"wordcount_map"')
|
352
|
-
end
|
353
|
-
|
354
|
-
it "should include the module and function when invoking an Erlang function" do
|
355
|
-
@phase.function = ["riak_mapreduce", "mapreduce_fun"]
|
356
|
-
@phase.to_json.should include('"module":"riak_mapreduce"')
|
357
|
-
@phase.to_json.should include('"function":"mapreduce_fun"')
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
describe "when a link phase" do
|
363
|
-
before :each do
|
364
|
-
@phase.type = :link
|
365
|
-
@phase.function = {}
|
366
|
-
end
|
367
|
-
|
368
|
-
it "should be an object of a single key 'link'" do
|
369
|
-
@phase.to_json.should =~ /^\{"link":/
|
370
|
-
end
|
371
|
-
|
372
|
-
it "should include the bucket" do
|
373
|
-
@phase.to_json.should =~ /"bucket":"_"/
|
374
|
-
@phase.function[:bucket] = "foo"
|
375
|
-
@phase.to_json.should =~ /"bucket":"foo"/
|
376
|
-
end
|
377
|
-
|
378
|
-
it "should include the tag" do
|
379
|
-
@phase.to_json.should =~ /"tag":"_"/
|
380
|
-
@phase.function[:tag] = "parent"
|
381
|
-
@phase.to_json.should =~ /"tag":"parent"/
|
382
|
-
end
|
383
|
-
|
384
|
-
it "should include the keep value" do
|
385
|
-
@phase.to_json.should =~ /"keep":false/
|
386
|
-
@phase.keep = true
|
387
|
-
@phase.to_json.should =~ /"keep":true/
|
388
|
-
@phase.keep = false
|
389
|
-
@phase.function[:keep] = true
|
390
|
-
@phase.to_json.should =~ /"keep":true/
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
data/spec/riak/object_spec.rb
CHANGED
@@ -103,7 +103,6 @@ describe Riak::RObject do
|
|
103
103
|
@payload = Marshal.dump({"foo" => "bar"})
|
104
104
|
end
|
105
105
|
|
106
|
-
|
107
106
|
it "should dump via Marshal" do
|
108
107
|
@object.serialize({"foo" => "bar"}).should == @payload
|
109
108
|
end
|
@@ -172,88 +171,6 @@ describe Riak::RObject do
|
|
172
171
|
end
|
173
172
|
end
|
174
173
|
|
175
|
-
describe "loading data from the response" do
|
176
|
-
before :each do
|
177
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
178
|
-
end
|
179
|
-
|
180
|
-
it "should load the content type" do
|
181
|
-
@object.load({:headers => {"content-type" => ["application/json"]}})
|
182
|
-
@object.content_type.should == "application/json"
|
183
|
-
end
|
184
|
-
|
185
|
-
it "should load the body data" do
|
186
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => '{"foo":"bar"}'})
|
187
|
-
@object.raw_data.should be_present
|
188
|
-
@object.data.should be_present
|
189
|
-
end
|
190
|
-
|
191
|
-
it "should handle raw data properly" do
|
192
|
-
@object.should_not_receive(:deserialize) # optimize for the raw_data case, don't penalize people for using raw_data
|
193
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => body = '{"foo":"bar"}'})
|
194
|
-
@object.raw_data.should == body
|
195
|
-
end
|
196
|
-
|
197
|
-
it "should deserialize the body data" do
|
198
|
-
@object.should_receive(:deserialize).with("{}").and_return({})
|
199
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => "{}"})
|
200
|
-
@object.data.should == {}
|
201
|
-
end
|
202
|
-
|
203
|
-
it "should leave the object data unchanged if the response body is blank" do
|
204
|
-
@object.data = "Original data"
|
205
|
-
@object.load({:headers => {"content-type" => ["application/json"]}, :body => ""})
|
206
|
-
@object.data.should == "Original data"
|
207
|
-
end
|
208
|
-
|
209
|
-
it "should load the vclock from the headers" do
|
210
|
-
@object.load({:headers => {"content-type" => ["application/json"], 'x-riak-vclock' => ["somereallylongbase64string=="]}, :body => "{}"})
|
211
|
-
@object.vclock.should == "somereallylongbase64string=="
|
212
|
-
end
|
213
|
-
|
214
|
-
it "should load links from the headers" do
|
215
|
-
@object.load({:headers => {"content-type" => ["application/json"], "link" => ['</riak/bar>; rel="up"']}, :body => "{}"})
|
216
|
-
@object.links.should have(1).item
|
217
|
-
@object.links.first.url.should == "/riak/bar"
|
218
|
-
@object.links.first.rel.should == "up"
|
219
|
-
end
|
220
|
-
|
221
|
-
it "should load the ETag from the headers" do
|
222
|
-
@object.load({:headers => {"content-type" => ["application/json"], "etag" => ["32748nvas83572934"]}, :body => "{}"})
|
223
|
-
@object.etag.should == "32748nvas83572934"
|
224
|
-
end
|
225
|
-
|
226
|
-
it "should load the modified date from the headers" do
|
227
|
-
time = Time.now
|
228
|
-
@object.load({:headers => {"content-type" => ["application/json"], "last-modified" => [time.httpdate]}, :body => "{}"})
|
229
|
-
@object.last_modified.to_s.should == time.to_s # bah, times are not equivalent unless equal
|
230
|
-
end
|
231
|
-
|
232
|
-
it "should load meta information from the headers" do
|
233
|
-
@object.load({:headers => {"content-type" => ["application/json"], "x-riak-meta-some-kind-of-robot" => ["for AWESOME"]}, :body => "{}"})
|
234
|
-
@object.meta["some-kind-of-robot"].should == ["for AWESOME"]
|
235
|
-
end
|
236
|
-
|
237
|
-
it "should parse the location header into the key when present" do
|
238
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz"]}})
|
239
|
-
@object.key.should == "baz"
|
240
|
-
end
|
241
|
-
|
242
|
-
it "should parse and escape the location header into the key when present" do
|
243
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/%5Bbaz%5D"]}})
|
244
|
-
@object.key.should == "[baz]"
|
245
|
-
end
|
246
|
-
|
247
|
-
it "should be in conflict when the response code is 300 and the content-type is multipart/mixed" do
|
248
|
-
@object.load({:headers => {"content-type" => ["multipart/mixed; boundary=foo"]}, :code => 300 })
|
249
|
-
@object.should be_conflict
|
250
|
-
end
|
251
|
-
|
252
|
-
it "should unescape the key given in the location header" do
|
253
|
-
@object.load({:headers => {"content-type" => ["application/json"], "location" => ["/riak/foo/baz%20"]}})
|
254
|
-
@object.key.should == "baz "
|
255
|
-
end
|
256
|
-
end
|
257
174
|
|
258
175
|
describe "instantiating new object from a map reduce operation" do
|
259
176
|
before :each do
|
@@ -355,144 +272,21 @@ describe Riak::RObject do
|
|
355
272
|
end
|
356
273
|
end
|
357
274
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
it "should extract the siblings" do
|
364
|
-
@object.should have(2).siblings
|
365
|
-
siblings = @object.siblings
|
366
|
-
siblings[0].data.should == "bar"
|
367
|
-
siblings[1].data.should == "baz"
|
368
|
-
end
|
369
|
-
|
370
|
-
it "should set the key on both siblings" do
|
371
|
-
@object.siblings.should be_all {|s| s.key == "bar" }
|
372
|
-
end
|
373
|
-
|
374
|
-
it "should set the vclock on both siblings to the merged vclock" do
|
375
|
-
@object.siblings.should be_all {|s| s.vclock == "merged" }
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
describe "headers used for storing the object" do
|
380
|
-
before :each do
|
381
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
382
|
-
end
|
383
|
-
|
384
|
-
it "should include the content type" do
|
385
|
-
@object.content_type = "application/json"
|
386
|
-
@object.store_headers["Content-Type"].should == "application/json"
|
387
|
-
end
|
388
|
-
|
389
|
-
it "should include the vclock when present" do
|
390
|
-
@object.vclock = "123445678990"
|
391
|
-
@object.store_headers["X-Riak-Vclock"].should == "123445678990"
|
392
|
-
end
|
393
|
-
|
394
|
-
it "should exclude the vclock when nil" do
|
395
|
-
@object.vclock = nil
|
396
|
-
@object.store_headers.should_not have_key("X-Riak-Vclock")
|
397
|
-
end
|
398
|
-
|
399
|
-
describe "when conditional PUTs are requested" do
|
400
|
-
before :each do
|
401
|
-
@object.prevent_stale_writes = true
|
402
|
-
end
|
403
|
-
|
404
|
-
it "should include an If-None-Match: * header" do
|
405
|
-
@object.store_headers.should have_key("If-None-Match")
|
406
|
-
@object.store_headers["If-None-Match"].should == "*"
|
407
|
-
end
|
408
|
-
|
409
|
-
it "should include an If-Match header with the etag when an etag is present" do
|
410
|
-
@object.etag = "foobar"
|
411
|
-
@object.store_headers.should have_key("If-Match")
|
412
|
-
@object.store_headers["If-Match"].should == @object.etag
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
describe "when links are defined" do
|
417
|
-
before :each do
|
418
|
-
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
419
|
-
end
|
420
|
-
|
421
|
-
it "should include a Link header with references to other objects" do
|
422
|
-
@object.store_headers.should have_key("Link")
|
423
|
-
@object.store_headers["Link"].should include('</riak/foo/baz>; riaktag="next"')
|
424
|
-
end
|
425
|
-
|
426
|
-
it "should exclude the 'up' link to the bucket from the header" do
|
427
|
-
@object.links << Riak::Link.new("/riak/foo", "up")
|
428
|
-
@object.store_headers.should have_key("Link")
|
429
|
-
@object.store_headers["Link"].should_not include('riaktag="up"')
|
430
|
-
end
|
431
|
-
|
432
|
-
it "should not allow duplicate links" do
|
433
|
-
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
434
|
-
@object.links.length.should == 1
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
it "should exclude the Link header when no links are present" do
|
439
|
-
@object.links = Set.new
|
440
|
-
@object.store_headers.should_not have_key("Link")
|
441
|
-
end
|
442
|
-
|
443
|
-
describe "when meta fields are present" do
|
444
|
-
before :each do
|
445
|
-
@object.meta = {"some-kind-of-robot" => true, "powers" => "for awesome", "cold-ones" => 10}
|
446
|
-
end
|
447
|
-
|
448
|
-
it "should include X-Riak-Meta-* headers for each meta key" do
|
449
|
-
@object.store_headers.should have_key("X-Riak-Meta-some-kind-of-robot")
|
450
|
-
@object.store_headers.should have_key("X-Riak-Meta-cold-ones")
|
451
|
-
@object.store_headers.should have_key("X-Riak-Meta-powers")
|
452
|
-
end
|
453
|
-
|
454
|
-
it "should turn non-string meta values into strings" do
|
455
|
-
@object.store_headers["X-Riak-Meta-some-kind-of-robot"].should == "true"
|
456
|
-
@object.store_headers["X-Riak-Meta-cold-ones"].should == "10"
|
457
|
-
end
|
458
|
-
|
459
|
-
it "should leave string meta values unchanged in the header" do
|
460
|
-
@object.store_headers["X-Riak-Meta-powers"].should == "for awesome"
|
461
|
-
end
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
describe "headers used for reloading the object" do
|
466
|
-
before :each do
|
467
|
-
@object = Riak::RObject.new(@bucket, "bar")
|
468
|
-
end
|
469
|
-
|
470
|
-
it "should be blank when the etag and last_modified properties are blank" do
|
471
|
-
@object.etag.should be_blank
|
472
|
-
@object.last_modified.should be_blank
|
473
|
-
@object.reload_headers.should be_blank
|
474
|
-
end
|
475
|
-
|
476
|
-
it "should include the If-None-Match key when the etag is present" do
|
477
|
-
@object.etag = "etag!"
|
478
|
-
@object.reload_headers['If-None-Match'].should == "etag!"
|
479
|
-
end
|
480
|
-
|
481
|
-
it "should include the If-Modified-Since header when the last_modified time is present" do
|
482
|
-
time = Time.now
|
483
|
-
@object.last_modified = time
|
484
|
-
@object.reload_headers['If-Modified-Since'].should == time.httpdate
|
485
|
-
end
|
275
|
+
it "should not allow duplicate links" do
|
276
|
+
@object = Riak::RObject.new(@bucket, "foo")
|
277
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
278
|
+
@object.links << Riak::Link.new("/riak/foo/baz", "next")
|
279
|
+
@object.links.length.should == 1
|
486
280
|
end
|
487
281
|
|
488
282
|
describe "when storing the object normally" do
|
489
283
|
before :each do
|
490
|
-
@
|
491
|
-
@client.stub!(:
|
284
|
+
@backend = mock("Backend")
|
285
|
+
@client.stub!(:backend).and_return(@backend)
|
492
286
|
@object = Riak::RObject.new(@bucket)
|
493
287
|
@object.content_type = "text/plain"
|
494
288
|
@object.data = "This is some text."
|
495
|
-
@headers = @object.store_headers
|
289
|
+
# @headers = @object.store_headers
|
496
290
|
end
|
497
291
|
|
498
292
|
it "should raise an error when the content_type is blank" do
|
@@ -500,124 +294,49 @@ describe Riak::RObject do
|
|
500
294
|
lambda { @object.content_type = " "; @object.store }.should raise_error(ArgumentError)
|
501
295
|
end
|
502
296
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
@object.store
|
507
|
-
@object.key.should == "somereallylongstring"
|
508
|
-
@object.vclock.should == "areallylonghashvalue"
|
509
|
-
end
|
510
|
-
|
511
|
-
it "should include persistence-tuning parameters in the query string" do
|
512
|
-
@http.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})
|
513
|
-
@object.store(:dw => 2)
|
514
|
-
end
|
515
|
-
|
516
|
-
it "should escape the bucket name" do
|
517
|
-
@bucket.should_receive(:name).and_return("foo ")
|
518
|
-
@http.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})
|
519
|
-
@object.store
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
describe "when the object has a key" do
|
524
|
-
before :each do
|
525
|
-
@object.key = "bar"
|
526
|
-
end
|
527
|
-
|
528
|
-
describe "when the content is of a known serializable type" do
|
529
|
-
before :each do
|
530
|
-
@object.content_type = "application/json"
|
531
|
-
@headers = @object.store_headers
|
532
|
-
end
|
533
|
-
|
534
|
-
it "should not serialize content if #raw_data is used" do
|
535
|
-
storing = @object.raw_data = "{this is probably invalid json}}"
|
536
|
-
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, storing, @headers).and_return({:headers => {"x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
537
|
-
@object.should_not_receive(:serialize)
|
538
|
-
@object.should_not_receive(:deserialize)
|
539
|
-
@object.store
|
540
|
-
@object.raw_data.should == storing
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
|
545
|
-
@http.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})
|
546
|
-
@object.store
|
547
|
-
@object.key.should == "somereallylongstring"
|
548
|
-
@object.vclock.should == "areallylonghashvalue"
|
549
|
-
end
|
550
|
-
|
551
|
-
it "should include persistence-tuning parameters in the query string" do
|
552
|
-
@http.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
|
553
|
-
@object.store(:dw => 2)
|
554
|
-
end
|
555
|
-
|
556
|
-
it "should escape the bucket and key names" do
|
557
|
-
@http.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})
|
558
|
-
@bucket.instance_variable_set(:@name, "foo ")
|
559
|
-
@object.key = "bar/baz"
|
560
|
-
@object.store
|
561
|
-
end
|
297
|
+
it "should pass along quorum parameters and returnbody to the backend" do
|
298
|
+
@backend.should_receive(:store_object).with(@object, false, 3, 2).and_return(true)
|
299
|
+
@object.store(:returnbody => false, :w => 3, :dw => 2)
|
562
300
|
end
|
563
301
|
end
|
564
302
|
|
565
303
|
describe "when reloading the object" do
|
566
304
|
before :each do
|
567
|
-
@
|
568
|
-
@client.stub!(:
|
305
|
+
@backend = mock("Backend")
|
306
|
+
@client.stub!(:backend).and_return(@backend)
|
569
307
|
@object = Riak::RObject.new(@bucket, "bar")
|
570
308
|
@object.vclock = "somereallylongstring"
|
571
|
-
@object.stub!(:reload_headers).and_return({})
|
572
309
|
end
|
573
310
|
|
574
311
|
it "should return without requesting if the key is blank" do
|
575
312
|
@object.key = nil
|
576
|
-
@
|
313
|
+
@backend.should_not_receive(:reload_object)
|
577
314
|
@object.reload
|
578
315
|
end
|
579
316
|
|
580
317
|
it "should return without requesting if the vclock is blank" do
|
581
318
|
@object.vclock = nil
|
582
|
-
@
|
583
|
-
@object.reload
|
584
|
-
end
|
585
|
-
|
586
|
-
it "should make the request if the key is present and the :force option is given" do
|
587
|
-
@http.should_receive(:get).and_return({:headers => {}, :code => 304})
|
588
|
-
@object.reload :force => true
|
589
|
-
end
|
590
|
-
|
591
|
-
it "should pass along the reload_headers" do
|
592
|
-
@headers = {"If-None-Match" => "etag"}
|
593
|
-
@object.should_receive(:reload_headers).and_return(@headers)
|
594
|
-
@http.should_receive(:get).with([200,304], "/riak/", "foo", "bar", {}, @headers).and_return({:code => 304})
|
319
|
+
@backend.should_not_receive(:reload_object)
|
595
320
|
@object.reload
|
596
321
|
end
|
597
322
|
|
598
|
-
it "should
|
599
|
-
@
|
600
|
-
@object.should_not_receive(:load)
|
323
|
+
it "should reload the object if the key is present" do
|
324
|
+
@backend.should_receive(:reload_object).with(@object, nil).and_return(@object)
|
601
325
|
@object.reload
|
602
326
|
end
|
603
327
|
|
604
|
-
it "should
|
605
|
-
@
|
606
|
-
@object.
|
607
|
-
lambda { @object.reload }.should raise_error(Riak::FailedRequest)
|
328
|
+
it "should pass along the requested R quorum" do
|
329
|
+
@backend.should_receive(:reload_object).with(@object, 2).and_return(@object)
|
330
|
+
@object.reload :r => 2
|
608
331
|
end
|
609
|
-
|
610
|
-
it "should
|
611
|
-
@
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
@bucket.should_receive(:name).and_return("some/deep/path")
|
618
|
-
@object.key = "another/deep/path"
|
619
|
-
@http.should_receive(:get).with([200,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
|
620
|
-
@object.reload
|
332
|
+
|
333
|
+
it "should disable matching conditions if the key is present and the :force option is given" do
|
334
|
+
@backend.should_receive(:reload_object) do |obj, _|
|
335
|
+
obj.etag.should be_nil
|
336
|
+
obj.last_modified.should be_nil
|
337
|
+
obj
|
338
|
+
end
|
339
|
+
@object.reload :force => true
|
621
340
|
end
|
622
341
|
end
|
623
342
|
|
@@ -627,70 +346,37 @@ describe Riak::RObject do
|
|
627
346
|
@client.stub!(:http).and_return(@http)
|
628
347
|
@client.stub!(:bucket).and_return(@bucket)
|
629
348
|
@object = Riak::RObject.new(@bucket, "bar")
|
630
|
-
@body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
|
631
|
-
end
|
632
|
-
|
633
|
-
it "should issue a GET request to the given walk spec" do
|
634
|
-
@http.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")
|
635
|
-
@object.walk(nil,"next",true)
|
636
349
|
end
|
637
350
|
|
638
|
-
it "should
|
639
|
-
@http.
|
640
|
-
|
641
|
-
results.should be_kind_of(Array)
|
642
|
-
results.first.should be_kind_of(Array)
|
643
|
-
obj = results.first.first
|
644
|
-
obj.should be_kind_of(Riak::RObject)
|
645
|
-
obj.content_type.should == "text/plain"
|
646
|
-
obj.key.should == "baz"
|
647
|
-
obj.bucket.should == @bucket
|
648
|
-
end
|
649
|
-
|
650
|
-
it "should assign the bucket for newly parsed objects" do
|
651
|
-
@http.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
|
652
|
-
@client.should_receive(:bucket).with("foo", :keys => false).and_return(@bucket)
|
653
|
-
@object.walk(nil,"next",true)
|
654
|
-
end
|
655
|
-
|
656
|
-
it "should escape the bucket, key and link specs" do
|
657
|
-
@object.key = "bar/baz"
|
658
|
-
@bucket.should_receive(:name).and_return("quin/quux")
|
659
|
-
@http.should_receive(:get).with(200, "/riak/", "quin%2Fquux", "bar%2Fbaz", "_,next%2F2,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")
|
660
|
-
@object.walk(:tag => "next/2", :keep => true)
|
351
|
+
it "should normalize the walk specs and submit the link-walking request to the HTTP backend" do
|
352
|
+
@http.should_receive(:link_walk).with(@object, [instance_of(Riak::WalkSpec)]).and_return([])
|
353
|
+
@object.walk(nil,"next",true).should == []
|
661
354
|
end
|
662
355
|
end
|
663
356
|
|
664
357
|
describe "when deleting" do
|
665
358
|
before :each do
|
666
|
-
@
|
667
|
-
@client.stub!(:
|
359
|
+
@backend = mock("Backend")
|
360
|
+
@client.stub!(:backend).and_return(@backend)
|
668
361
|
@object = Riak::RObject.new(@bucket, "bar")
|
669
362
|
end
|
670
363
|
|
671
364
|
it "should make a DELETE request to the Riak server and freeze the object" do
|
672
|
-
@
|
365
|
+
@backend.should_receive(:delete_object).with(@bucket, "bar", nil)
|
673
366
|
@object.delete
|
674
367
|
@object.should be_frozen
|
675
368
|
end
|
676
369
|
|
677
370
|
it "should do nothing when the key is blank" do
|
678
|
-
@
|
371
|
+
@backend.should_not_receive(:delete_object)
|
679
372
|
@object.key = nil
|
680
373
|
@object.delete
|
681
374
|
end
|
682
375
|
|
683
376
|
it "should pass through a failed request exception" do
|
684
|
-
@
|
377
|
+
@backend.should_receive(:delete_object).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
|
685
378
|
lambda { @object.delete }.should raise_error(Riak::FailedRequest)
|
686
379
|
end
|
687
|
-
|
688
|
-
it "should escape the bucket and key names" do
|
689
|
-
@object.key = "deep/path"
|
690
|
-
@bucket.should_receive(:name).and_return("bucket spaces")
|
691
|
-
@http.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
|
692
|
-
@object.delete
|
693
|
-
end
|
694
380
|
end
|
695
381
|
|
696
382
|
it "should not convert to link without a tag" do
|