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