riak-client 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Rakefile +74 -0
  2. data/lib/riak.rb +49 -0
  3. data/lib/riak/bucket.rb +176 -0
  4. data/lib/riak/cache_store.rb +82 -0
  5. data/lib/riak/client.rb +139 -0
  6. data/lib/riak/client/curb_backend.rb +82 -0
  7. data/lib/riak/client/http_backend.rb +209 -0
  8. data/lib/riak/client/net_http_backend.rb +49 -0
  9. data/lib/riak/failed_request.rb +37 -0
  10. data/lib/riak/i18n.rb +20 -0
  11. data/lib/riak/invalid_response.rb +25 -0
  12. data/lib/riak/link.rb +73 -0
  13. data/lib/riak/locale/en.yml +37 -0
  14. data/lib/riak/map_reduce.rb +248 -0
  15. data/lib/riak/map_reduce_error.rb +20 -0
  16. data/lib/riak/robject.rb +267 -0
  17. data/lib/riak/util/escape.rb +12 -0
  18. data/lib/riak/util/fiber1.8.rb +48 -0
  19. data/lib/riak/util/headers.rb +44 -0
  20. data/lib/riak/util/multipart.rb +52 -0
  21. data/lib/riak/util/translation.rb +29 -0
  22. data/lib/riak/walk_spec.rb +117 -0
  23. data/spec/fixtures/cat.jpg +0 -0
  24. data/spec/fixtures/multipart-blank.txt +7 -0
  25. data/spec/fixtures/multipart-with-body.txt +16 -0
  26. data/spec/integration/riak/cache_store_spec.rb +129 -0
  27. data/spec/riak/bucket_spec.rb +247 -0
  28. data/spec/riak/client_spec.rb +174 -0
  29. data/spec/riak/curb_backend_spec.rb +53 -0
  30. data/spec/riak/escape_spec.rb +21 -0
  31. data/spec/riak/headers_spec.rb +34 -0
  32. data/spec/riak/http_backend_spec.rb +131 -0
  33. data/spec/riak/link_spec.rb +82 -0
  34. data/spec/riak/map_reduce_spec.rb +352 -0
  35. data/spec/riak/multipart_spec.rb +36 -0
  36. data/spec/riak/net_http_backend_spec.rb +28 -0
  37. data/spec/riak/object_spec.rb +538 -0
  38. data/spec/riak/walk_spec_spec.rb +208 -0
  39. data/spec/spec_helper.rb +30 -0
  40. data/spec/support/http_backend_implementation_examples.rb +215 -0
  41. data/spec/support/mock_server.rb +61 -0
  42. data/spec/support/mocks.rb +3 -0
  43. 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