riak-client 0.7.0
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 +74 -0
- data/lib/riak.rb +49 -0
- data/lib/riak/bucket.rb +176 -0
- data/lib/riak/cache_store.rb +82 -0
- data/lib/riak/client.rb +139 -0
- data/lib/riak/client/curb_backend.rb +82 -0
- data/lib/riak/client/http_backend.rb +209 -0
- data/lib/riak/client/net_http_backend.rb +49 -0
- data/lib/riak/failed_request.rb +37 -0
- data/lib/riak/i18n.rb +20 -0
- data/lib/riak/invalid_response.rb +25 -0
- data/lib/riak/link.rb +73 -0
- data/lib/riak/locale/en.yml +37 -0
- data/lib/riak/map_reduce.rb +248 -0
- data/lib/riak/map_reduce_error.rb +20 -0
- data/lib/riak/robject.rb +267 -0
- data/lib/riak/util/escape.rb +12 -0
- data/lib/riak/util/fiber1.8.rb +48 -0
- data/lib/riak/util/headers.rb +44 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/translation.rb +29 -0
- data/lib/riak/walk_spec.rb +117 -0
- data/spec/fixtures/cat.jpg +0 -0
- data/spec/fixtures/multipart-blank.txt +7 -0
- data/spec/fixtures/multipart-with-body.txt +16 -0
- data/spec/integration/riak/cache_store_spec.rb +129 -0
- data/spec/riak/bucket_spec.rb +247 -0
- data/spec/riak/client_spec.rb +174 -0
- data/spec/riak/curb_backend_spec.rb +53 -0
- data/spec/riak/escape_spec.rb +21 -0
- data/spec/riak/headers_spec.rb +34 -0
- data/spec/riak/http_backend_spec.rb +131 -0
- data/spec/riak/link_spec.rb +82 -0
- data/spec/riak/map_reduce_spec.rb +352 -0
- data/spec/riak/multipart_spec.rb +36 -0
- data/spec/riak/net_http_backend_spec.rb +28 -0
- data/spec/riak/object_spec.rb +538 -0
- data/spec/riak/walk_spec_spec.rb +208 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/http_backend_implementation_examples.rb +215 -0
- data/spec/support/mock_server.rb +61 -0
- data/spec/support/mocks.rb +3 -0
- metadata +187 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Riak::Util::Escape do
|
4
|
+
before :each do
|
5
|
+
@object = Object.new
|
6
|
+
@object.extend(Riak::Util::Escape)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should escape standard non-safe characters" do
|
10
|
+
@object.escape("some string").should == "some%20string"
|
11
|
+
@object.escape("another^one").should == "another%5Eone"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should escape slashes" do
|
15
|
+
@object.escape("some/inner/path").should == "some%2Finner%2Fpath"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should convert the bucket or key to a string before escaping" do
|
19
|
+
@object.escape(125).should == '125'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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::Util::Headers do
|
17
|
+
it "should include the Net::HTTPHeader module" do
|
18
|
+
Riak::Util::Headers.included_modules.should include(Net::HTTPHeader)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be initially empty" do
|
22
|
+
Riak::Util::Headers.new.to_hash.should == {}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should parse a header line into the key and value" do
|
26
|
+
Riak::Util::Headers.parse("Content-Type: text/plain\n").should == ["Content-Type", "text/plain"]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should parse a header line and add it to the collection" do
|
30
|
+
h = Riak::Util::Headers.new
|
31
|
+
h.parse("Content-Type: text/plain\n")
|
32
|
+
h.to_hash.should == {"content-type" => ["text/plain"]}
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,131 @@
|
|
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 do
|
17
|
+
before :each do
|
18
|
+
@client = Riak::Client.new
|
19
|
+
@backend = Riak::Client::HTTPBackend.new(@client)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should take the Riak::Client when creating" do
|
23
|
+
lambda { Riak::Client::HTTPBackend.new(nil) }.should raise_error(ArgumentError)
|
24
|
+
lambda { Riak::Client::HTTPBackend.new(@client) }.should_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should make the client accessible" do
|
28
|
+
@backend.client.should == @client
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should generate default headers for requests based on the client settings" do
|
32
|
+
@client.client_id = "testing"
|
33
|
+
@backend.default_headers.should == {"X-Riak-ClientId" => "testing", "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5"}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should generate a root URI based on the client settings" do
|
37
|
+
@backend.root_uri.should be_kind_of(URI)
|
38
|
+
@backend.root_uri.to_s.should == "http://127.0.0.1:8098"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should compute a URI from a relative resource path" do
|
42
|
+
@backend.path("baz").should be_kind_of(URI)
|
43
|
+
@backend.path("foo").to_s.should == "http://127.0.0.1:8098/foo"
|
44
|
+
@backend.path("foo", "bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
|
45
|
+
@backend.path("/foo/bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should compute a URI from a relative resource path with a hash of query parameters" do
|
49
|
+
@backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise an error if a resource path is too short" do
|
53
|
+
lambda { @backend.verify_path!(["/riak/"]) }.should raise_error(ArgumentError)
|
54
|
+
lambda { @backend.verify_path!(["/riak/", "foo"]) }.should_not raise_error
|
55
|
+
lambda { @backend.verify_path!(["/mapred"]) }.should_not raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "verify_path_and_body!" do
|
59
|
+
it "should separate the path and body from given arguments" do
|
60
|
+
uri, data = @backend.verify_path_and_body!(["/riak/", "foo", "This is the body."])
|
61
|
+
uri.should == ["/riak/", "foo"]
|
62
|
+
data.should == "This is the body."
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise an error if the body is not a string or IO" do
|
66
|
+
lambda { @backend.verify_path_and_body!(["/riak/", "foo", nil]) }.should raise_error(ArgumentError)
|
67
|
+
lambda { @backend.verify_path_and_body!(["/riak/", "foo", File.open("spec/fixtures/cat.jpg")]) }.should_not raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should raise an error if a body is not given" do
|
71
|
+
lambda { @backend.verify_path_and_body!(["/riak/", "foo"])}.should raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should raise an error if a path is not given" do
|
75
|
+
lambda { @backend.verify_path_and_body!(["/riak/"])}.should raise_error(ArgumentError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "detecting valid response codes" do
|
80
|
+
it "should accept strings or integers for either argument" do
|
81
|
+
@backend.should be_valid_response("300", "300")
|
82
|
+
@backend.should be_valid_response(300, "300")
|
83
|
+
@backend.should be_valid_response("300", 300)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should accept an array of strings or integers for the expected code" do
|
87
|
+
@backend.should be_valid_response([200,304], "200")
|
88
|
+
@backend.should be_valid_response(["200",304], "200")
|
89
|
+
@backend.should be_valid_response([200,"304"], "200")
|
90
|
+
@backend.should be_valid_response(["200","304"], "200")
|
91
|
+
@backend.should be_valid_response([200,304], 200)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be false when none of the response codes match" do
|
95
|
+
@backend.should_not be_valid_response(200, 404)
|
96
|
+
@backend.should_not be_valid_response(["200","304"], 404)
|
97
|
+
@backend.should_not be_valid_response([200,304], 404)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "detecting whether a body should be returned" do
|
102
|
+
it "should be false when the method is :head" do
|
103
|
+
@backend.should_not be_return_body(:head, 200, false)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should be false when the response code is 204, 205, or 304" do
|
107
|
+
@backend.should_not be_return_body(:get, 204, false)
|
108
|
+
@backend.should_not be_return_body(:get, 205, false)
|
109
|
+
@backend.should_not be_return_body(:get, 304, false)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should be false when a streaming block was passed" do
|
113
|
+
@backend.should_not be_return_body(:get, 200, true)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should be true when the method is not head, a code other than 204, 205, or 304 was given, and there was no streaming block" do
|
117
|
+
[:get, :put, :post, :delete].each do |method|
|
118
|
+
[100,101,200,201,202,203,206,300,301,302,303,305,307,400,401,
|
119
|
+
402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,
|
120
|
+
500,501,502,503,504,505].each do |code|
|
121
|
+
@backend.should be_return_body(method, code, false)
|
122
|
+
@backend.should be_return_body(method, code.to_s, false)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should force subclasses to implement the perform method" do
|
129
|
+
lambda { @backend.send(:perform, :get, "/foo", {}, 200) }.should raise_error(NotImplementedError)
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,82 @@
|
|
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::Link do
|
17
|
+
describe "parsing a link header" do
|
18
|
+
it "should create Link objects from the data" do
|
19
|
+
result = Riak::Link.parse('</riak/foo/bar>; rel="tag", </riak/foo>; rel="up"')
|
20
|
+
result.should be_kind_of(Array)
|
21
|
+
result.should be_all {|i| Riak::Link === i }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should set the bucket, key, url and rel parameters properly" do
|
25
|
+
result = Riak::Link.parse('</riak/foo/bar>; riaktag="tag", </riak/foo>; rel="up"')
|
26
|
+
result[0].url.should == "/riak/foo/bar"
|
27
|
+
result[0].bucket.should == "foo"
|
28
|
+
result[0].key.should == "bar"
|
29
|
+
result[0].rel.should == "tag"
|
30
|
+
result[1].url.should == "/riak/foo"
|
31
|
+
result[1].bucket.should == "foo"
|
32
|
+
result[1].key.should == nil
|
33
|
+
result[1].rel.should == "up"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set url properly, and set bucket and key to nil for non-Riak links" do
|
37
|
+
result = Riak::Link.parse('<http://www.example.com/123.html>; riaktag="tag", </riak/foo>; rel="up"')
|
38
|
+
result[0].url.should == "http://www.example.com/123.html"
|
39
|
+
result[0].bucket.should == nil
|
40
|
+
result[0].key.should == nil
|
41
|
+
result[0].rel.should == "tag"
|
42
|
+
|
43
|
+
result = Riak::Link.parse('<http://www.example.com/>; riaktag="tag", </riak/foo>; rel="up"')
|
44
|
+
result[0].url.should == "http://www.example.com/"
|
45
|
+
result[0].bucket.should == nil
|
46
|
+
result[0].key.should == nil
|
47
|
+
result[0].rel.should == "tag"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should convert to a string appropriate for use in the Link header" do
|
52
|
+
Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
|
53
|
+
Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should convert to a walk spec when pointing to an object" do
|
57
|
+
Riak::Link.new("/riak/foo/bar", "next").to_walk_spec.to_s.should == "foo,next,_"
|
58
|
+
lambda { Riak::Link.new("/riak/foo", "up").to_walk_spec }.should raise_error
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should be equivalent to a link with the same url and rel" do
|
62
|
+
one = Riak::Link.new("/riak/foo/bar", "next")
|
63
|
+
two = Riak::Link.new("/riak/foo/bar", "next")
|
64
|
+
one.should == two
|
65
|
+
[one].should include(two)
|
66
|
+
[two].should include(one)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should unescape the bucket name" do
|
70
|
+
Riak::Link.new("/riak/bucket%20spaces/key", "foo").bucket.should == "bucket spaces"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should unescape the key name" do
|
74
|
+
Riak::Link.new("/riak/bucket/key%2Fname", "foo").key.should == "key/name"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not rely on the prefix to equal /riak/ when extracting the bucket and key" do
|
78
|
+
link = Riak::Link.new("/raw/bucket/key", "foo")
|
79
|
+
link.bucket.should == "bucket"
|
80
|
+
link.key.should == "key"
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,352 @@
|
|
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::MapReduce do
|
17
|
+
before :each do
|
18
|
+
@client = Riak::Client.new
|
19
|
+
@http = mock("HTTPBackend")
|
20
|
+
@client.stub!(:http).and_return(@http)
|
21
|
+
@mr = Riak::MapReduce.new(@client)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should require a client" do
|
25
|
+
lambda { Riak::MapReduce.new }.should raise_error
|
26
|
+
lambda { Riak::MapReduce.new(@client) }.should_not raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should initialize the inputs and query to empty arrays" do
|
30
|
+
@mr.inputs.should == []
|
31
|
+
@mr.query.should == []
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should yield itself when given a block on initializing" do
|
35
|
+
@mr2 = nil
|
36
|
+
@mr = Riak::MapReduce.new(@client) do |mr|
|
37
|
+
@mr2 = mr
|
38
|
+
end
|
39
|
+
@mr2.should == @mr
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "adding inputs" do
|
43
|
+
it "should return self for chaining" do
|
44
|
+
@mr.add("foo", "bar").should == @mr
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should add bucket/key pairs to the inputs" do
|
48
|
+
@mr.add("foo","bar")
|
49
|
+
@mr.inputs.should == [["foo","bar"]]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should add an array containing a bucket/key pair to the inputs" do
|
53
|
+
@mr.add(["foo","bar"])
|
54
|
+
@mr.inputs.should == [["foo","bar"]]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should add an object to the inputs by its bucket and key" do
|
58
|
+
bucket = Riak::Bucket.new(@client, "foo")
|
59
|
+
obj = Riak::RObject.new(bucket, "bar")
|
60
|
+
@mr.add(obj)
|
61
|
+
@mr.inputs.should == [["foo", "bar"]]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should add an array containing a bucket/key/key-data triple to the inputs" do
|
65
|
+
@mr.add(["foo","bar",1000])
|
66
|
+
@mr.inputs.should == [["foo","bar",1000]]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should use a bucket name as the single input" do
|
70
|
+
@mr.add(Riak::Bucket.new(@client, "foo"))
|
71
|
+
@mr.inputs.should == "foo"
|
72
|
+
@mr.add("docs")
|
73
|
+
@mr.inputs.should == "docs"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
[:map, :reduce].each do |type|
|
78
|
+
describe "adding #{type} phases" do
|
79
|
+
it "should return self for chaining" do
|
80
|
+
@mr.send(type, "function(){}").should == @mr
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should accept a function string" do
|
84
|
+
@mr.send(type, "function(){}")
|
85
|
+
@mr.query.should have(1).items
|
86
|
+
phase = @mr.query.first
|
87
|
+
phase.function.should == "function(){}"
|
88
|
+
phase.type.should == type
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should accept a function and options" do
|
92
|
+
@mr.send(type, "function(){}", :keep => true)
|
93
|
+
@mr.query.should have(1).items
|
94
|
+
phase = @mr.query.first
|
95
|
+
phase.function.should == "function(){}"
|
96
|
+
phase.type.should == type
|
97
|
+
phase.keep.should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should accept a module/function pair" do
|
101
|
+
@mr.send(type, ["riak","mapsomething"])
|
102
|
+
@mr.query.should have(1).items
|
103
|
+
phase = @mr.query.first
|
104
|
+
phase.function.should == ["riak", "mapsomething"]
|
105
|
+
phase.type.should == type
|
106
|
+
phase.language.should == "erlang"
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should accept a module/function pair with extra options" do
|
110
|
+
@mr.send(type, ["riak", "mapsomething"], :arg => [1000])
|
111
|
+
@mr.query.should have(1).items
|
112
|
+
phase = @mr.query.first
|
113
|
+
phase.function.should == ["riak", "mapsomething"]
|
114
|
+
phase.type.should == type
|
115
|
+
phase.language.should == "erlang"
|
116
|
+
phase.arg.should == [1000]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "adding link phases" do
|
122
|
+
it "should return self for chaining" do
|
123
|
+
@mr.link({}).should == @mr
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should accept a WalkSpec" do
|
127
|
+
@mr.link(Riak::WalkSpec.new(:tag => "next"))
|
128
|
+
@mr.query.should have(1).items
|
129
|
+
phase = @mr.query.first
|
130
|
+
phase.type.should == :link
|
131
|
+
phase.function.should be_kind_of(Riak::WalkSpec)
|
132
|
+
phase.function.tag.should == "next"
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should accept a WalkSpec and a hash of options" do
|
136
|
+
@mr.link(Riak::WalkSpec.new(:bucket => "foo"), :keep => true)
|
137
|
+
@mr.query.should have(1).items
|
138
|
+
phase = @mr.query.first
|
139
|
+
phase.type.should == :link
|
140
|
+
phase.function.should be_kind_of(Riak::WalkSpec)
|
141
|
+
phase.function.bucket.should == "foo"
|
142
|
+
phase.keep.should be_true
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should accept a hash of options intermingled with the walk spec options" do
|
146
|
+
@mr.link(:tag => "snakes", :arg => [1000])
|
147
|
+
@mr.query.should have(1).items
|
148
|
+
phase = @mr.query.first
|
149
|
+
phase.arg.should == [1000]
|
150
|
+
phase.function.should be_kind_of(Riak::WalkSpec)
|
151
|
+
phase.function.tag.should == "snakes"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "converting to JSON for the job" do
|
156
|
+
it "should include the inputs and query keys" do
|
157
|
+
@mr.to_json.should =~ /"inputs":/
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should map phases to their JSON equivalents" do
|
161
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => "function(){}")
|
162
|
+
@mr.query << phase
|
163
|
+
@mr.to_json.should include('"source":"function(){}"')
|
164
|
+
@mr.to_json.should include('"query":[{"map":{')
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should emit only the bucket name when the input is the whole bucket" do
|
168
|
+
@mr.add("foo")
|
169
|
+
@mr.to_json.should include('"inputs":"foo"')
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should emit an array of inputs when there are multiple inputs" do
|
173
|
+
@mr.add("foo","bar",1000).add("foo","baz")
|
174
|
+
@mr.to_json.should include('"inputs":[["foo","bar",1000],["foo","baz"]]')
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should add the timeout value when set" do
|
178
|
+
@mr.timeout(50000)
|
179
|
+
@mr.to_json.should include('"timeout":50000')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "executing the map reduce job" do
|
184
|
+
it "should issue POST request to the mapred endpoint" do
|
185
|
+
@http.should_receive(:post).with(200, "/mapred", @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "{}"})
|
186
|
+
@mr.run
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should vivify JSON responses" do
|
190
|
+
@http.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '{"key":"value"}')
|
191
|
+
@mr.run.should == {"key" => "value"}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should return the full response hash for non-JSON responses" do
|
195
|
+
response = {:code => 200, :headers => {'content-type' => ["text/plain"]}, :body => 'This is some text.'}
|
196
|
+
@http.stub!(:post).and_return(response)
|
197
|
+
@mr.run.should == response
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should interpret failed requests with JSON content-types as map reduce errors" do
|
201
|
+
@http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
|
202
|
+
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
203
|
+
begin
|
204
|
+
@mr.run
|
205
|
+
rescue Riak::MapReduceError => mre
|
206
|
+
mre.message.should == '{"error":"syntax error"}'
|
207
|
+
else
|
208
|
+
fail "No exception raised!"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should re-raise non-JSON error responses" do
|
213
|
+
@http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
|
214
|
+
lambda { @mr.run }.should raise_error(Riak::FailedRequest)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe Riak::MapReduce::Phase do
|
220
|
+
before :each do
|
221
|
+
@fun = "function(v,_,_){ return v['values'][0]['data']; }"
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should initialize with a type and a function" do
|
225
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun, :language => "javascript")
|
226
|
+
phase.type.should == :map
|
227
|
+
phase.function.should == @fun
|
228
|
+
phase.language.should == "javascript"
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should initialize with a type and an MF" do
|
232
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"], :language => "erlang")
|
233
|
+
phase.type.should == :map
|
234
|
+
phase.function.should == ["module", "function"]
|
235
|
+
phase.language.should == "erlang"
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should initialize with a type and a bucket/key" do
|
239
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "funs", :key => "awesome_map"}, :language => "javascript")
|
240
|
+
phase.type.should == :map
|
241
|
+
phase.function.should == {:bucket => "funs", :key => "awesome_map"}
|
242
|
+
phase.language.should == "javascript"
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should assume the language is erlang when the function is an array" do
|
246
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"])
|
247
|
+
phase.language.should == "erlang"
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should assume the language is javascript when the function is a string" do
|
251
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun)
|
252
|
+
phase.language.should == "javascript"
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should assume the language is javascript when the function is a hash" do
|
256
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "jobs", :key => "awesome_map"})
|
257
|
+
phase.language.should == "javascript"
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should accept a WalkSpec for the function when a link phase" do
|
261
|
+
phase = Riak::MapReduce::Phase.new(:type => :link, :function => Riak::WalkSpec.new({}))
|
262
|
+
phase.function.should be_kind_of(Riak::WalkSpec)
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should raise an error if a WalkSpec is given for a phase type other than :link" do
|
266
|
+
lambda { Riak::MapReduce::Phase.new(:type => :map, :function => Riak::WalkSpec.new({})) }.should raise_error(ArgumentError)
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "converting to JSON for the job" do
|
270
|
+
before :each do
|
271
|
+
@phase = Riak::MapReduce::Phase.new(:type => :map, :function => "")
|
272
|
+
end
|
273
|
+
|
274
|
+
[:map, :reduce].each do |type|
|
275
|
+
describe "when a #{type} phase" do
|
276
|
+
before :each do
|
277
|
+
@phase.type = type
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should be an object with a single key of '#{type}'" do
|
281
|
+
@phase.to_json.should =~ /^\{"#{type}":/
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should include the language" do
|
285
|
+
@phase.to_json.should =~ /"language":/
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should include the keep value" do
|
289
|
+
@phase.to_json.should =~ /"keep":false/
|
290
|
+
@phase.keep = true
|
291
|
+
@phase.to_json.should =~ /"keep":true/
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should include the function source when the function is a source string" do
|
295
|
+
@phase.function = "function(v,_,_){ return v; }"
|
296
|
+
@phase.to_json.should include(@phase.function)
|
297
|
+
@phase.to_json.should =~ /"source":/
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should include the function name when the function is not a lambda" do
|
301
|
+
@phase.function = "Riak.mapValues"
|
302
|
+
@phase.to_json.should include('"name":"Riak.mapValues"')
|
303
|
+
@phase.to_json.should_not include('"source"')
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should include the bucket and key when referring to a stored function" do
|
307
|
+
@phase.function = {:bucket => "design", :key => "wordcount_map"}
|
308
|
+
@phase.to_json.should include('"bucket":"design"')
|
309
|
+
@phase.to_json.should include('"key":"wordcount_map"')
|
310
|
+
end
|
311
|
+
|
312
|
+
it "should include the module and function when invoking an Erlang function" do
|
313
|
+
@phase.function = ["riak_mapreduce", "mapreduce_fun"]
|
314
|
+
@phase.to_json.should include('"module":"riak_mapreduce"')
|
315
|
+
@phase.to_json.should include('"function":"mapreduce_fun"')
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe "when a link phase" do
|
321
|
+
before :each do
|
322
|
+
@phase.type = :link
|
323
|
+
@phase.function = {}
|
324
|
+
end
|
325
|
+
|
326
|
+
it "should be an object of a single key 'link'" do
|
327
|
+
@phase.to_json.should =~ /^\{"link":/
|
328
|
+
end
|
329
|
+
|
330
|
+
it "should include the bucket" do
|
331
|
+
@phase.to_json.should =~ /"bucket":"_"/
|
332
|
+
@phase.function[:bucket] = "foo"
|
333
|
+
@phase.to_json.should =~ /"bucket":"foo"/
|
334
|
+
end
|
335
|
+
|
336
|
+
it "should include the tag" do
|
337
|
+
@phase.to_json.should =~ /"tag":"_"/
|
338
|
+
@phase.function[:tag] = "parent"
|
339
|
+
@phase.to_json.should =~ /"tag":"parent"/
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should include the keep value" do
|
343
|
+
@phase.to_json.should =~ /"keep":false/
|
344
|
+
@phase.keep = true
|
345
|
+
@phase.to_json.should =~ /"keep":true/
|
346
|
+
@phase.keep = false
|
347
|
+
@phase.function[:keep] = true
|
348
|
+
@phase.to_json.should =~ /"keep":true/
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|