seomoz-riak-client 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/Gemfile +27 -0
  2. data/Guardfile +14 -0
  3. data/Rakefile +76 -0
  4. data/erl_src/riak_kv_test_backend.beam +0 -0
  5. data/erl_src/riak_kv_test_backend.erl +174 -0
  6. data/erl_src/riak_search_test_backend.beam +0 -0
  7. data/erl_src/riak_search_test_backend.erl +175 -0
  8. data/lib/active_support/cache/riak_store.rb +2 -0
  9. data/lib/riak.rb +21 -0
  10. data/lib/riak/bucket.rb +215 -0
  11. data/lib/riak/cache_store.rb +84 -0
  12. data/lib/riak/client.rb +415 -0
  13. data/lib/riak/client/beefcake/messages.rb +147 -0
  14. data/lib/riak/client/beefcake/object_methods.rb +92 -0
  15. data/lib/riak/client/beefcake_protobuffs_backend.rb +176 -0
  16. data/lib/riak/client/excon_backend.rb +65 -0
  17. data/lib/riak/client/http_backend.rb +203 -0
  18. data/lib/riak/client/http_backend/configuration.rb +46 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +94 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +218 -0
  23. data/lib/riak/client/net_http_backend.rb +79 -0
  24. data/lib/riak/client/protobuffs_backend.rb +97 -0
  25. data/lib/riak/client/pump.rb +30 -0
  26. data/lib/riak/client/search.rb +94 -0
  27. data/lib/riak/core_ext.rb +6 -0
  28. data/lib/riak/core_ext/blank.rb +53 -0
  29. data/lib/riak/core_ext/extract_options.rb +7 -0
  30. data/lib/riak/core_ext/json.rb +15 -0
  31. data/lib/riak/core_ext/slice.rb +18 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  33. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  34. data/lib/riak/core_ext/to_param.rb +31 -0
  35. data/lib/riak/encoding.rb +6 -0
  36. data/lib/riak/failed_request.rb +81 -0
  37. data/lib/riak/i18n.rb +3 -0
  38. data/lib/riak/json.rb +28 -0
  39. data/lib/riak/link.rb +85 -0
  40. data/lib/riak/locale/en.yml +48 -0
  41. data/lib/riak/map_reduce.rb +206 -0
  42. data/lib/riak/map_reduce/filter_builder.rb +94 -0
  43. data/lib/riak/map_reduce/phase.rb +98 -0
  44. data/lib/riak/map_reduce_error.rb +7 -0
  45. data/lib/riak/robject.rb +290 -0
  46. data/lib/riak/search.rb +3 -0
  47. data/lib/riak/serializers.rb +74 -0
  48. data/lib/riak/stamp.rb +77 -0
  49. data/lib/riak/test_server.rb +252 -0
  50. data/lib/riak/util/escape.rb +45 -0
  51. data/lib/riak/util/fiber1.8.rb +48 -0
  52. data/lib/riak/util/headers.rb +53 -0
  53. data/lib/riak/util/multipart.rb +52 -0
  54. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  55. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  56. data/lib/riak/util/translation.rb +19 -0
  57. data/lib/riak/walk_spec.rb +105 -0
  58. data/riak-client.gemspec +55 -0
  59. data/seomoz-riak-client.gemspec +55 -0
  60. data/spec/fixtures/cat.jpg +0 -0
  61. data/spec/fixtures/multipart-blank.txt +7 -0
  62. data/spec/fixtures/multipart-mapreduce.txt +10 -0
  63. data/spec/fixtures/multipart-with-body.txt +16 -0
  64. data/spec/fixtures/server.cert.crt +15 -0
  65. data/spec/fixtures/server.cert.key +15 -0
  66. data/spec/fixtures/test.pem +1 -0
  67. data/spec/integration/riak/cache_store_spec.rb +154 -0
  68. data/spec/integration/riak/http_backends_spec.rb +58 -0
  69. data/spec/integration/riak/protobuffs_backends_spec.rb +32 -0
  70. data/spec/integration/riak/test_server_spec.rb +161 -0
  71. data/spec/riak/beefcake_protobuffs_backend_spec.rb +59 -0
  72. data/spec/riak/bucket_spec.rb +205 -0
  73. data/spec/riak/client_spec.rb +517 -0
  74. data/spec/riak/core_ext/to_param_spec.rb +15 -0
  75. data/spec/riak/escape_spec.rb +69 -0
  76. data/spec/riak/excon_backend_spec.rb +64 -0
  77. data/spec/riak/headers_spec.rb +38 -0
  78. data/spec/riak/http_backend/configuration_spec.rb +51 -0
  79. data/spec/riak/http_backend/object_methods_spec.rb +217 -0
  80. data/spec/riak/http_backend/transport_methods_spec.rb +117 -0
  81. data/spec/riak/http_backend_spec.rb +269 -0
  82. data/spec/riak/link_spec.rb +71 -0
  83. data/spec/riak/map_reduce/filter_builder_spec.rb +32 -0
  84. data/spec/riak/map_reduce/phase_spec.rb +136 -0
  85. data/spec/riak/map_reduce_spec.rb +310 -0
  86. data/spec/riak/multipart_spec.rb +23 -0
  87. data/spec/riak/net_http_backend_spec.rb +16 -0
  88. data/spec/riak/robject_spec.rb +427 -0
  89. data/spec/riak/search_spec.rb +178 -0
  90. data/spec/riak/serializers_spec.rb +93 -0
  91. data/spec/riak/stamp_spec.rb +54 -0
  92. data/spec/riak/stream_parser_spec.rb +53 -0
  93. data/spec/riak/walk_spec_spec.rb +195 -0
  94. data/spec/spec_helper.rb +39 -0
  95. data/spec/support/drb_mock_server.rb +39 -0
  96. data/spec/support/http_backend_implementation_examples.rb +266 -0
  97. data/spec/support/integration_setup.rb +10 -0
  98. data/spec/support/mock_server.rb +81 -0
  99. data/spec/support/mocks.rb +4 -0
  100. data/spec/support/test_server.yml.example +2 -0
  101. data/spec/support/unified_backend_examples.rb +255 -0
  102. metadata +271 -0
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Client::HTTPBackend::TransportMethods do
4
+ before :each do
5
+ @client = Riak::Client.new
6
+ @backend = Riak::Client::HTTPBackend.new(@client)
7
+ @backend.instance_variable_set(:@server_config, {})
8
+ end
9
+
10
+ it "should generate default headers for requests based on the client settings" do
11
+ @client.client_id = "testing"
12
+ @backend.default_headers.should == {"X-Riak-ClientId" => "testing", "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5"}
13
+ end
14
+
15
+ it "should generate a root URI based on the client settings" do
16
+ @backend.root_uri.should be_kind_of(URI)
17
+ @backend.root_uri.to_s.should == "http://127.0.0.1:8098"
18
+ end
19
+
20
+ it "should compute a URI from a relative resource path" do
21
+ @backend.path("baz").should be_kind_of(URI)
22
+ @backend.path("foo").to_s.should == "http://127.0.0.1:8098/foo"
23
+ @backend.path("foo", "bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
24
+ @backend.path("/foo/bar").to_s.should == "http://127.0.0.1:8098/foo/bar"
25
+ end
26
+
27
+ it "should compute a URI from a relative resource path with a hash of query parameters" do
28
+ @backend.path("baz", :r => 2).to_s.should == "http://127.0.0.1:8098/baz?r=2"
29
+ end
30
+
31
+ it "should raise an error if a resource path is too short" do
32
+ lambda { @backend.verify_path!(["/riak/"]) }.should raise_error(ArgumentError)
33
+ lambda { @backend.verify_path!(["/riak/", "foo"]) }.should_not raise_error
34
+ lambda { @backend.verify_path!(["/mapred"]) }.should_not raise_error
35
+ end
36
+
37
+ describe "verify_path_and_body!" do
38
+ it "should separate the path and body from given arguments" do
39
+ uri, data = @backend.verify_path_and_body!(["/riak/", "foo", "This is the body."])
40
+ uri.should == ["/riak/", "foo"]
41
+ data.should == "This is the body."
42
+ end
43
+
44
+ it "should raise an error if the body is not a string or IO or IO-like (responds to :read)" do
45
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo", nil]) }.should raise_error(ArgumentError)
46
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo", File.open("spec/fixtures/cat.jpg")]) }.should_not raise_error(ArgumentError)
47
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo", Tempfile.new('riak-spec')]) }.should_not raise_error(ArgumentError)
48
+ end
49
+
50
+ it "should raise an error if a body is not given" do
51
+ lambda { @backend.verify_path_and_body!(["/riak/", "foo"])}.should raise_error(ArgumentError)
52
+ end
53
+
54
+ it "should raise an error if a path is not given" do
55
+ lambda { @backend.verify_path_and_body!(["/riak/"])}.should raise_error(ArgumentError)
56
+ end
57
+ end
58
+
59
+ describe "detecting valid response codes" do
60
+ it "should accept strings or integers for either argument" do
61
+ @backend.should be_valid_response("300", "300")
62
+ @backend.should be_valid_response(300, "300")
63
+ @backend.should be_valid_response("300", 300)
64
+ end
65
+
66
+ it "should accept an array of strings or integers for the expected code" do
67
+ @backend.should be_valid_response([200,304], "200")
68
+ @backend.should be_valid_response(["200",304], "200")
69
+ @backend.should be_valid_response([200,"304"], "200")
70
+ @backend.should be_valid_response(["200","304"], "200")
71
+ @backend.should be_valid_response([200,304], 200)
72
+ end
73
+
74
+ it "should be false when none of the response codes match" do
75
+ @backend.should_not be_valid_response(200, 404)
76
+ @backend.should_not be_valid_response(["200","304"], 404)
77
+ @backend.should_not be_valid_response([200,304], 404)
78
+ end
79
+ end
80
+
81
+ describe "detecting whether a body should be returned" do
82
+ it "should be false when the method is :head" do
83
+ @backend.should_not be_return_body(:head, 200, false)
84
+ end
85
+
86
+ it "should be false when the response code is 204, 205, or 304" do
87
+ @backend.should_not be_return_body(:get, 204, false)
88
+ @backend.should_not be_return_body(:get, 205, false)
89
+ @backend.should_not be_return_body(:get, 304, false)
90
+ end
91
+
92
+ it "should be false when a streaming block was passed" do
93
+ @backend.should_not be_return_body(:get, 200, true)
94
+ end
95
+
96
+ 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
97
+ [:get, :put, :post, :delete].each do |method|
98
+ [100,101,200,201,202,203,206,300,301,302,303,305,307,400,401,
99
+ 402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,
100
+ 500,501,502,503,504,505].each do |code|
101
+ @backend.should be_return_body(method, code, false)
102
+ @backend.should be_return_body(method, code.to_s, false)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ it "should force subclasses to implement the perform method" do
109
+ lambda { @backend.send(:perform, :get, "/foo", {}, 200) }.should raise_error(NotImplementedError)
110
+ end
111
+
112
+ it "should allow using the https protocol" do
113
+ @client = Riak::Client.new(:protocol => 'https')
114
+ @backend = Riak::Client::HTTPBackend.new(@client)
115
+ @backend.root_uri.to_s.should eq("https://127.0.0.1:8098")
116
+ end
117
+ end
@@ -0,0 +1,269 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Client::HTTPBackend do
4
+ before :each do
5
+ @client = Riak::Client.new
6
+ @backend = Riak::Client::HTTPBackend.new(@client)
7
+ @backend.instance_variable_set(:@server_config, {})
8
+ end
9
+
10
+ it "should take the Riak::Client when creating" do
11
+ lambda { Riak::Client::HTTPBackend.new(nil) }.should raise_error(ArgumentError)
12
+ lambda { Riak::Client::HTTPBackend.new(@client) }.should_not raise_error
13
+ end
14
+
15
+ it "should make the client accessible" do
16
+ @backend.client.should == @client
17
+ end
18
+
19
+ context "pinging the server" do
20
+ it "should succeed on 200" do
21
+ @backend.should_receive(:get).with(200, "/ping", {}, {}).and_return({:code => 200, :body => "OK"})
22
+ @backend.ping.should be_true
23
+ end
24
+
25
+ it "should fail on any other code or error" do
26
+ @backend.should_receive(:get).and_raise("socket closed")
27
+ @backend.ping.should be_false
28
+ end
29
+ end
30
+
31
+ context "fetching an object" do
32
+ it "should perform a GET request and return an RObject" do
33
+ @backend.should_receive(:get).with([200,300], "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
34
+ @backend.fetch_object("foo", "db").should be_kind_of(Riak::RObject)
35
+ end
36
+
37
+ it "should pass the R quorum as a query parameter" do
38
+ @backend.should_receive(:get).with([200,300], "/riak/","foo", "db", {:r => 2}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
39
+ @backend.fetch_object("foo", "db", 2)
40
+ end
41
+
42
+ it "should escape the bucket and key names" do
43
+ @backend.should_receive(:get).with([200,300], "/riak/","foo%20", "%20bar", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
44
+ @backend.fetch_object('foo ',' bar').should be_kind_of(Riak::RObject)
45
+ end
46
+ end
47
+
48
+ context "reloading an object" do
49
+ before do
50
+ @object = Riak::RObject.new(@client.bucket("foo"), "bar")
51
+ end
52
+
53
+ it "should use conditional request headers" do
54
+ @object.etag = "etag"
55
+ @backend.should_receive(:get).with([200,300,304], "/riak/", "foo", "bar", {}, {'If-None-Match' => "etag"}).and_return({:code => 304})
56
+ @backend.reload_object(@object)
57
+ end
58
+
59
+ it "should return without modifying the object if the response is 304 Not Modified" do
60
+ @backend.should_receive(:get).and_return({:code => 304})
61
+ @backend.should_not_receive(:load_object)
62
+ @backend.reload_object(@object)
63
+ end
64
+
65
+ it "should raise an exception when the response code is not 200 or 304" do
66
+ @backend.should_receive(:get).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 500, {}, ''))
67
+ lambda { @backend.reload_object(@object) }.should raise_error(Riak::FailedRequest)
68
+ end
69
+
70
+ it "should escape the bucket and key names" do
71
+ # @bucket.should_receive(:name).and_return("some/deep/path")
72
+ @object.bucket = @client.bucket("some/deep/path")
73
+ @object.key = "another/deep/path"
74
+ @backend.should_receive(:get).with([200,300,304], "/riak/", "some%2Fdeep%2Fpath", "another%2Fdeep%2Fpath", {}, {}).and_return({:code => 304})
75
+ @backend.reload_object(@object)
76
+ end
77
+ end
78
+
79
+ context "storing an object" do
80
+ before do
81
+ @bucket = Riak::Bucket.new(@client, "foo")
82
+ @object = Riak::RObject.new(@bucket)
83
+ @object.content_type = "text/plain"
84
+ @object.data = "This is some text."
85
+ @headers = @backend.store_headers(@object)
86
+ end
87
+
88
+ it "should use the raw_data as the request body" do
89
+ @object.content_type = "application/json"
90
+ body = @object.raw_data = "{this is probably invalid json!}}"
91
+ @backend.stub(:post).and_return({})
92
+ @object.should_not_receive(:serialize)
93
+ @backend.store_object(@object, false)
94
+ end
95
+
96
+ context "when the object has no key" do
97
+ it "should issue a POST request to the bucket, and update the object properties (returning the body by default)" do
98
+ @backend.should_receive(:post).with(201, "/riak/", "foo", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
99
+ @backend.store_object(@object, true, nil, nil)
100
+ @object.key.should == "somereallylongstring"
101
+ @object.vclock.should == "areallylonghashvalue"
102
+ end
103
+
104
+ it "should include persistence-tuning parameters in the query string" do
105
+ @backend.should_receive(:post).with(201, "/riak/", "foo", {:dw => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
106
+ @backend.store_object(@object, true, nil, 2)
107
+ end
108
+
109
+ it "should escape the bucket name" do
110
+ @object.bucket = @client.bucket("foo ")
111
+ @backend.should_receive(:post).with(201, "/riak/", "foo%20", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 201})
112
+ @backend.store_object(@object, true)
113
+ end
114
+ end
115
+
116
+ context "when the object has a key" do
117
+ before :each do
118
+ @object.key = "bar"
119
+ end
120
+
121
+ it "should issue a PUT request to the bucket, and update the object properties (returning the body by default)" do
122
+ @backend.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
123
+ @backend.store_object(@object, true, nil, nil)
124
+ @object.key.should == "somereallylongstring"
125
+ @object.vclock.should == "areallylonghashvalue"
126
+ end
127
+
128
+ it "should include persistence-tuning parameters in the query string" do
129
+ @backend.should_receive(:put).with([200,204,300], "/riak/", "foo/bar", {:w => 2, :returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
130
+ @backend.store_object(@object, true, 2, nil)
131
+ end
132
+
133
+ it "should escape the bucket and key names" do
134
+ @backend.should_receive(:put).with([200,204,300], "/riak/", "foo%20/bar%2Fbaz", {:returnbody => true}, "This is some text.", @headers).and_return({:headers => {'location' => ["/riak/foo/somereallylongstring"], "x-riak-vclock" => ["areallylonghashvalue"]}, :code => 204})
135
+ @bucket.instance_variable_set(:@name, "foo ")
136
+ @object.key = "bar/baz"
137
+ @backend.store_object(@object, true, nil, nil)
138
+ end
139
+ end
140
+ end
141
+
142
+ context "deleting an object" do
143
+ it "should perform a DELETE request" do
144
+ @backend.should_receive(:delete).with([204,404], "/riak/", "foo", 'bar',{},{}).and_return(:code => 204)
145
+ @backend.delete_object("foo", "bar")
146
+ end
147
+
148
+ it "should escape the bucket and key names" do
149
+ @backend.should_receive(:delete).with([204,404], "/riak/", "bucket%20spaces", "deep%2Fpath",{},{}).and_return({:code => 204, :headers => {}})
150
+ @backend.delete_object("bucket spaces", "deep/path")
151
+ end
152
+ end
153
+
154
+ context "fetching bucket properties" do
155
+ it "should GET the bucket URL and parse the response as JSON" do
156
+ @backend.should_receive(:get).with(200, "/riak/", "foo", {:keys => false, :props => true}, {}).and_return({:body => '{"props":{"n_val":3}}'})
157
+ @backend.get_bucket_props("foo").should == {"n_val" => 3}
158
+ end
159
+
160
+ it "should escape the bucket name" do
161
+ @backend.should_receive(:get).with(200, "/riak/", "foo%20bar", {:keys => false, :props => true}, {}).and_return({:body => '{"props":{"n_val":3}}'})
162
+ @backend.get_bucket_props("foo bar")
163
+ end
164
+ end
165
+
166
+ context "setting bucket properties" do
167
+ it "should PUT the properties to the bucket URL as JSON" do
168
+ @backend.should_receive(:put).with(204, "/riak/","foo", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
169
+ @backend.set_bucket_props("foo", {:n_val => 2})
170
+ end
171
+
172
+ it "should escape the bucket name" do
173
+ @backend.should_receive(:put).with(204, "/riak/","foo%20bar", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
174
+ @backend.set_bucket_props("foo bar", {:n_val => 2})
175
+ end
176
+ end
177
+
178
+ context "listing keys" do
179
+ it "should unescape key names" do
180
+ @backend.should_receive(:get).with(200, "/riak/","foo", {:props => false, :keys => true}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar%20baz"]}'})
181
+ @backend.list_keys("foo").should == ["bar baz"]
182
+ end
183
+
184
+ it "should escape the bucket name" do
185
+ @backend.should_receive(:get).with(200, "/riak/","unescaped%20", {:props => false, :keys => true}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
186
+ @backend.list_keys("unescaped ").should == ["bar"]
187
+ end
188
+ end
189
+
190
+ context "listing buckets" do
191
+ it "should GET the raw URL with ?buckets=true and parse the response as JSON" do
192
+ @backend.should_receive(:get).with(200, "/riak/", {:buckets => true}, {}).and_return({:body => '{"buckets":["foo", "bar", "baz"]}'})
193
+ @backend.list_buckets.should == ["foo", "bar", "baz"]
194
+ end
195
+ end
196
+
197
+ context "performing a MapReduce query" do
198
+ before do
199
+ @mr = Riak::MapReduce.new(@client).map("Riak.mapValues", :keep => true)
200
+ end
201
+
202
+ it "should issue POST request to the mapred endpoint" do
203
+ @backend.should_receive(:post).with(200, "/mapred", @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "[]"})
204
+ @backend.mapred(@mr)
205
+ end
206
+
207
+ it "should vivify JSON responses" do
208
+ @backend.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '[{"key":"value"}]')
209
+ @backend.mapred(@mr).should == [{"key" => "value"}]
210
+ end
211
+
212
+ it "should return the full response hash for non-JSON responses" do
213
+ response = {:code => 200, :headers => {'content-type' => ["text/plain"]}, :body => 'This is some text.'}
214
+ @backend.stub!(:post).and_return(response)
215
+ @backend.mapred(@mr).should == response
216
+ end
217
+
218
+ it "should stream results through the block" do
219
+ data = File.read("spec/fixtures/multipart-mapreduce.txt")
220
+ @backend.should_receive(:post).with(200, "/mapred", {:chunked => true}, @mr.to_json, hash_including("Content-Type" => "application/json")).and_yield(data)
221
+ block = mock
222
+ block.should_receive(:ping).twice.and_return(true)
223
+ @backend.mapred(@mr) do |phase, data|
224
+ block.ping
225
+ phase.should == 0
226
+ data.should have(1).item
227
+ end
228
+ end
229
+ end
230
+
231
+ context "getting statistics" do
232
+ it "should get the status URL and parse the response as JSON" do
233
+ @backend.should_receive(:get).with(200, "/stats", {}, {}).and_return({:body => '{"vnode_gets":20348}'})
234
+ @backend.stats.should == {"vnode_gets" => 20348}
235
+ end
236
+ end
237
+
238
+ context "performing a link-walking query" do
239
+ before do
240
+ @bucket = Riak::Bucket.new(@client, "foo")
241
+ @object = Riak::RObject.new(@bucket, "bar")
242
+ @body = File.read(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/multipart-with-body.txt"))
243
+ @specs = [Riak::WalkSpec.new(:tag => "next", :keep => true)]
244
+ end
245
+
246
+ it "should perform a GET request for the given object and walk specs" do
247
+ @backend.should_receive(:get).with(200, "/riak/", "foo", "bar", "_,next,1").and_return(:headers => {"content-type" => ["multipart/mixed; boundary=12345"]}, :body => "\n--12345\nContent-Type: multipart/mixed; boundary=09876\n\n--09876--\n\n--12345--\n")
248
+ @backend.link_walk(@object, @specs)
249
+ end
250
+
251
+ it "should parse the results into arrays of objects" do
252
+ @backend.should_receive(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
253
+ results = @backend.link_walk(@object, @specs)
254
+ results.should be_kind_of(Array)
255
+ results.first.should be_kind_of(Array)
256
+ obj = results.first.first
257
+ obj.should be_kind_of(Riak::RObject)
258
+ obj.content_type.should == "text/plain"
259
+ obj.key.should == "baz"
260
+ obj.bucket.should == @bucket
261
+ end
262
+
263
+ it "should assign the bucket for newly parsed objects" do
264
+ @backend.stub!(:get).and_return(:headers => {"content-type" => ["multipart/mixed; boundary=5EiMOjuGavQ2IbXAqsJPLLfJNlA"]}, :body => @body)
265
+ @client.should_receive(:bucket).with("foo").and_return(@bucket)
266
+ @backend.link_walk(@object, @specs)
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Link do
4
+ describe "parsing a link header" do
5
+ it "should create Link objects from the data" do
6
+ result = Riak::Link.parse('</riak/foo/bar>; rel="tag", </riak/foo>; rel="up"')
7
+ result.should be_kind_of(Array)
8
+ result.should be_all {|i| Riak::Link === i }
9
+ end
10
+
11
+ it "should set the bucket, key, url and rel parameters properly" do
12
+ result = Riak::Link.parse('</riak/foo/bar>; riaktag="tag", </riak/foo>; rel="up"')
13
+ result[0].url.should == "/riak/foo/bar"
14
+ result[0].bucket.should == "foo"
15
+ result[0].key.should == "bar"
16
+ result[0].rel.should == "tag"
17
+ result[1].url.should == "/riak/foo"
18
+ result[1].bucket.should == "foo"
19
+ result[1].key.should == nil
20
+ result[1].rel.should == "up"
21
+ end
22
+
23
+ it "should keep the url intact if it does not point to a bucket or bucket/key" do
24
+ result = Riak::Link.parse('</mapred>; rel="riak_kv_wm_mapred"')
25
+ result[0].url.should == "/mapred"
26
+ result[0].bucket.should be_nil
27
+ result[0].key.should be_nil
28
+ end
29
+ end
30
+
31
+ it "should convert to a string appropriate for use in the Link header" do
32
+ Riak::Link.new("/riak/foo", "up").to_s.should == '</riak/foo>; riaktag="up"'
33
+ Riak::Link.new("/riak/foo/bar", "next").to_s.should == '</riak/foo/bar>; riaktag="next"'
34
+ Riak::Link.new("/riak", "riak_kv_wm_raw").to_s.should == '</riak>; riaktag="riak_kv_wm_raw"'
35
+ end
36
+
37
+ it "should convert to a walk spec when pointing to an object" do
38
+ Riak::Link.new("/riak/foo/bar", "next").to_walk_spec.to_s.should == "foo,next,_"
39
+ lambda { Riak::Link.new("/riak/foo", "up").to_walk_spec }.should raise_error
40
+ end
41
+
42
+ it "should be equivalent to a link with the same url and rel" do
43
+ one = Riak::Link.new("/riak/foo/bar", "next")
44
+ two = Riak::Link.new("/riak/foo/bar", "next")
45
+ one.should == two
46
+ [one].should include(two)
47
+ [two].should include(one)
48
+ end
49
+
50
+ it "should unescape the bucket name" do
51
+ Riak::Link.new("/riak/bucket%20spaces/key", "foo").bucket.should == "bucket spaces"
52
+ end
53
+
54
+ it "should unescape the key name" do
55
+ Riak::Link.new("/riak/bucket/key%2Fname", "foo").key.should == "key/name"
56
+ end
57
+
58
+ it "should not rely on the prefix to equal /riak/ when extracting the bucket and key" do
59
+ link = Riak::Link.new("/raw/bucket/key", "foo")
60
+ link.bucket.should == "bucket"
61
+ link.key.should == "key"
62
+ end
63
+
64
+ it "should construct from bucket, key and tag" do
65
+ link = Riak::Link.new("bucket", "key", "tag")
66
+ link.bucket.should == "bucket"
67
+ link.key.should == "key"
68
+ link.tag.should == "tag"
69
+ link.url.should == "/riak/bucket/key"
70
+ end
71
+ end