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,59 @@
1
+ require 'spec_helper'
2
+ require 'riak/client/beefcake/messages'
3
+
4
+ describe Riak::Client::BeefcakeProtobuffsBackend do
5
+ before :each do
6
+ @client = Riak::Client.new
7
+ @backend = Riak::Client::BeefcakeProtobuffsBackend.new(@client)
8
+ @backend.instance_variable_set(:@server_config, {})
9
+ end
10
+
11
+ it "should only write to the socket one time per request" do
12
+ exp_bucket, exp_keys = 'foo', ['bar']
13
+ mock_socket = mock("mock TCP socket")
14
+
15
+ @backend.stub!(:socket).and_return(mock_socket)
16
+ mock_socket.should_receive(:write).exactly(1).with do |param|
17
+ len, code = param[0,5].unpack("NC")
18
+ req = Riak::Client::BeefcakeProtobuffsBackend::RpbListKeysReq.decode(param[5..-1])
19
+ code == 17 && req.bucket == exp_bucket
20
+ end
21
+
22
+ responses = Array.new(2) do |index|
23
+ resp = Riak::Client::BeefcakeProtobuffsBackend::RpbListKeysResp.new
24
+ if index == 0
25
+ resp.keys = exp_keys
26
+ else
27
+ resp.done = true
28
+ end
29
+ resp
30
+ end
31
+
32
+ responses.each do |response|
33
+ encoded_response = response.encode
34
+ mock_socket.should_receive(:read).exactly(1).with(5).and_return([1 + encoded_response.length, 18].pack("NC"))
35
+ mock_socket.should_receive(:read).exactly(1).with(encoded_response.length).and_return(encoded_response)
36
+ end
37
+
38
+ @backend.list_keys(exp_bucket).should == exp_keys
39
+ end
40
+ end
41
+
42
+ describe Riak::Client::BeefcakeProtobuffsBackend, '#mapred' do
43
+ before(:each) do
44
+ @client = Riak::Client.new
45
+ @backend = Riak::Client::BeefcakeProtobuffsBackend.new(@client)
46
+ @backend.instance_variable_set(:@server_config, {})
47
+ end
48
+
49
+ it "should not return nil for previous phases that don't return anything" do
50
+ socket = stub(:socket).as_null_object
51
+ socket.stub(:read).and_return(stub(:socket_header, :unpack => [2, 24]), stub(:socket_message), stub(:socket_header_2, :unpack => [0, 1]))
52
+ message = stub(:message, :phase => 1, :response => [{}].to_json)
53
+ message.stub(:done).and_return(false, true)
54
+ Riak::Client::BeefcakeProtobuffsBackend::RpbMapRedResp.stub(:decode => message)
55
+ TCPSocket.stub(:new => socket)
56
+
57
+ @backend.mapred('').should == [{}]
58
+ end
59
+ end
@@ -0,0 +1,205 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Bucket do
4
+ before :each do
5
+ @client = Riak::Client.new
6
+ @backend = mock("Backend")
7
+ @client.stub!(:backend).and_return(@backend)
8
+ @bucket = Riak::Bucket.new(@client, "foo")
9
+ end
10
+
11
+ describe "when initializing" do
12
+ it "should require a client and a name" do
13
+ lambda { Riak::Bucket.new }.should raise_error
14
+ lambda { Riak::Bucket.new(@client) }.should raise_error
15
+ lambda { Riak::Bucket.new("foo") }.should raise_error
16
+ lambda { Riak::Bucket.new("foo", @client) }.should raise_error
17
+ lambda { Riak::Bucket.new(@client, "foo") }.should_not raise_error
18
+ end
19
+
20
+ it "should set the client and name attributes" do
21
+ bucket = Riak::Bucket.new(@client, "foo")
22
+ bucket.client.should == @client
23
+ bucket.name.should == "foo"
24
+ end
25
+ end
26
+
27
+ describe "accessing keys" do
28
+ it "should list the keys" do
29
+ @backend.should_receive(:list_keys).with(@bucket).and_return(["bar"])
30
+ @bucket.keys.should == ["bar"]
31
+ end
32
+
33
+ it "should allow streaming keys through block" do
34
+ @backend.should_receive(:list_keys).with(@bucket).and_yield([]).and_yield(["bar"]).and_yield(["baz"])
35
+ all_keys = []
36
+ @bucket.keys do |list|
37
+ all_keys.concat(list)
38
+ end
39
+ all_keys.should == ["bar", "baz"]
40
+ end
41
+
42
+ it "should not cache the list of keys" do
43
+ @backend.should_receive(:list_keys).with(@bucket).twice.and_return(["bar"])
44
+ 2.times { @bucket.keys.should == ['bar'] }
45
+ end
46
+
47
+ it "should warn about the expense of list-keys when warnings are not disabled" do
48
+ Riak.disable_list_keys_warnings = false
49
+ @backend.stub!(:list_keys).and_return(%w{test test2})
50
+ @bucket.should_receive(:warn)
51
+ @bucket.keys
52
+ Riak.disable_list_keys_warnings = true
53
+ end
54
+ end
55
+
56
+ describe "setting the bucket properties" do
57
+ it "should prefetch the properties when they are not present" do
58
+ @backend.stub!(:set_bucket_props)
59
+ @backend.should_receive(:get_bucket_props).with(@bucket).and_return({"name" => "foo"})
60
+ @bucket.props = {"precommit" => []}
61
+ end
62
+
63
+ it "should set the new properties on the bucket" do
64
+ @bucket.instance_variable_set(:@props, {}) # Pretend they are there
65
+ @backend.should_receive(:set_bucket_props).with(@bucket, { :name => "foo" })
66
+ @bucket.props = { :name => "foo" }
67
+ end
68
+
69
+ it "should raise an error if an invalid type is given" do
70
+ lambda { @bucket.props = "blah" }.should raise_error(ArgumentError)
71
+ end
72
+ end
73
+
74
+ describe "fetching the bucket properties" do
75
+ it "should fetch properties on first access" do
76
+ @bucket.instance_variable_get(:@props).should be_nil
77
+ @backend.should_receive(:get_bucket_props).with(@bucket).and_return({"name" => "foo"})
78
+ @bucket.props.should == {"name" => "foo"}
79
+ end
80
+
81
+ it "should memoize fetched properties" do
82
+ @backend.should_receive(:get_bucket_props).once.with(@bucket).and_return({"name" => "foo"})
83
+ @bucket.props.should == {"name" => "foo"}
84
+ @bucket.props.should == {"name" => "foo"}
85
+ end
86
+ end
87
+
88
+ describe "fetching an object" do
89
+ it "should fetch the object via the backend" do
90
+ @backend.should_receive(:fetch_object).with(@bucket, "db", nil).and_return(nil)
91
+ @bucket.get("db")
92
+ end
93
+
94
+ it "should use the specified R quroum" do
95
+ @backend.should_receive(:fetch_object).with(@bucket, "db", 2).and_return(nil)
96
+ @bucket.get("db", :r => 2)
97
+ end
98
+ end
99
+
100
+ describe "creating a new blank object" do
101
+ it "should instantiate the object with the given key, default to JSON" do
102
+ obj = @bucket.new('bar')
103
+ obj.should be_kind_of(Riak::RObject)
104
+ obj.key.should == 'bar'
105
+ obj.content_type.should == 'application/json'
106
+ end
107
+ end
108
+
109
+ describe "fetching or creating a new object" do
110
+ it "should return the existing object if present" do
111
+ @object = mock("RObject")
112
+ @backend.should_receive(:fetch_object).with(@bucket,"db", nil).and_return(@object)
113
+ @bucket.get_or_new('db').should == @object
114
+ end
115
+
116
+ it "should create a new blank object if the key does not exist" do
117
+ @backend.should_receive(:fetch_object).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 404, {}, "File not found"))
118
+ obj = @bucket.get_or_new('db')
119
+ obj.key.should == 'db'
120
+ obj.data.should be_blank
121
+ end
122
+
123
+ it "should bubble up non-ok non-missing errors" do
124
+ @backend.should_receive(:fetch_object).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 500, {}, "File not found"))
125
+ lambda { @bucket.get_or_new('db') }.should raise_error(Riak::HTTPFailedRequest)
126
+ end
127
+
128
+ it "should pass along the given R quorum parameter" do
129
+ @object = mock("RObject")
130
+ @backend.should_receive(:fetch_object).with(@bucket,"db", "all").and_return(@object)
131
+ @bucket.get_or_new('db', :r => "all").should == @object
132
+ end
133
+ end
134
+
135
+ describe "get/set allow_mult property" do
136
+ before :each do
137
+ @backend.stub!(:get_bucket_props).and_return({"allow_mult" => false})
138
+ end
139
+
140
+ it "should extract the allow_mult property" do
141
+ @bucket.allow_mult.should be_false
142
+ end
143
+
144
+ it "should set the allow_mult property" do
145
+ @bucket.should_receive(:props=).with(hash_including('allow_mult' => true))
146
+ @bucket.allow_mult = true
147
+ end
148
+ end
149
+
150
+ describe "get/set the N value" do
151
+ before :each do
152
+ @backend.stub!(:get_bucket_props).and_return({"n_val" => 3})
153
+ end
154
+
155
+ it "should extract the N value" do
156
+ @bucket.n_value.should == 3
157
+ end
158
+
159
+ it "should set the N value" do
160
+ @bucket.should_receive(:props=).with(hash_including('n_val' => 1))
161
+ @bucket.n_value = 1
162
+ end
163
+ end
164
+
165
+ [:r, :w, :dw, :rw].each do |q|
166
+ describe "get/set the default #{q} quorum" do
167
+ before :each do
168
+ @backend.stub!(:get_bucket_props).and_return({"r" => "quorum", "w" => "quorum", "dw" => "quorum", "rw" => "quorum"})
169
+ end
170
+
171
+ it "should extract the default #{q} quorum" do
172
+ @bucket.send(q).should == "quorum"
173
+ end
174
+
175
+ it "should set the #{q} quorum" do
176
+ @bucket.should_receive(:props=).with(hash_including("#{q}" => 1))
177
+ @bucket.send("#{q}=",1)
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "checking whether a key exists" do
183
+ it "should return true if the object does exist" do
184
+ @backend.should_receive(:fetch_object).and_return(mock)
185
+ @bucket.exists?("foo").should be_true
186
+ end
187
+
188
+ it "should return false if the object doesn't exist" do
189
+ @backend.should_receive(:fetch_object).and_raise(Riak::HTTPFailedRequest.new(:get, [200,300], 404, {}, "not found"))
190
+ @bucket.exists?("foo").should be_false
191
+ end
192
+ end
193
+
194
+ describe "deleting an object" do
195
+ it "should delete a key from within the bucket" do
196
+ @backend.should_receive(:delete_object).with(@bucket, "bar", nil)
197
+ @bucket.delete('bar')
198
+ end
199
+
200
+ it "should use the specified RW quorum" do
201
+ @backend.should_receive(:delete_object).with(@bucket, "bar", "all")
202
+ @bucket.delete('bar', :rw => "all")
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,517 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Client do
4
+ describe "when initializing" do
5
+ it "should default to the local interface on port 8098 (8087 for protobuffs)" do
6
+ client = Riak::Client.new
7
+ client.host.should == "127.0.0.1"
8
+ client.http_port.should == 8098
9
+ client.pb_port.should == 8087
10
+ end
11
+
12
+ it "should accept a protocol" do
13
+ client = Riak::Client.new :protocol => 'https'
14
+ client.protocol.should eq('https')
15
+ end
16
+
17
+ it "should accept a host" do
18
+ client = Riak::Client.new :host => "riak.basho.com"
19
+ client.host.should == "riak.basho.com"
20
+ end
21
+
22
+ it "should accept an HTTP port" do
23
+ client = Riak::Client.new :http_port => 9000
24
+ client.http_port.should == 9000
25
+ end
26
+
27
+ it "should accept a Protobuffs port" do
28
+ client = Riak::Client.new :pb_port => 9000
29
+ client.pb_port.should == 9000
30
+ end
31
+
32
+ it "should warn on setting :port" do
33
+ # TODO: make a deprecation utility class/module instead
34
+ client = Riak::Client.allocate
35
+ client.should_receive(:warn).and_return(true)
36
+ client.send :initialize, :port => 9000
37
+ end
38
+
39
+ it "should accept basic_auth" do
40
+ client = Riak::Client.new :basic_auth => "user:pass"
41
+ client.basic_auth.should eq("user:pass")
42
+ end
43
+
44
+ it "should accept a client ID" do
45
+ client = Riak::Client.new :client_id => "AAAAAA=="
46
+ client.client_id.should == "AAAAAA=="
47
+ end
48
+
49
+ it "should create a client ID if not specified" do
50
+ Riak::Client.new.client_id.should_not be_nil
51
+ end
52
+
53
+ it "should accept a path prefix" do
54
+ client = Riak::Client.new(:prefix => "/jiak/")
55
+ client.prefix.should == "/jiak/"
56
+ end
57
+
58
+ it "should default the prefix to /riak/ if not specified" do
59
+ Riak::Client.new.prefix.should == "/riak/"
60
+ end
61
+
62
+ it "should accept a mapreduce path" do
63
+ client = Riak::Client.new(:mapred => "/mr")
64
+ client.mapred.should == "/mr"
65
+ end
66
+
67
+ it "should default the mapreduce path to /mapred if not specified" do
68
+ Riak::Client.new.mapred.should == "/mapred"
69
+ end
70
+
71
+ it "should accept a luwak path" do
72
+ client = Riak::Client.new(:luwak => "/beans")
73
+ client.luwak.should == "/beans"
74
+ end
75
+
76
+ it "should default the luwak path to /luwak if not specified" do
77
+ Riak::Client.new.luwak.should == "/luwak"
78
+ end
79
+
80
+ it "should accept a solr path" do
81
+ client = Riak::Client.new(:solr => "/solar")
82
+ client.solr.should == "/solar"
83
+ end
84
+ end
85
+
86
+ it "should expose a Stamp object" do
87
+ subject.should respond_to(:stamp)
88
+ subject.stamp.should be_kind_of(Riak::Stamp)
89
+ end
90
+
91
+ describe "reconfiguring" do
92
+ before :each do
93
+ @client = Riak::Client.new
94
+ end
95
+
96
+ describe "setting the protocol" do
97
+ it "should allow setting the protocol" do
98
+ @client.should respond_to(:protocol=)
99
+ @client.protocol = "https"
100
+ @client.protocol.should eq("https")
101
+ end
102
+
103
+ it "should require a valid protocol to be set" do
104
+ lambda { @client.protocol = 'invalid-protocol' }.should(
105
+ raise_error(ArgumentError, /^'invalid-protocol' is not a valid protocol/))
106
+ end
107
+
108
+ it "should reset the unified backend when changing the protocol" do
109
+ old = @client.backend
110
+ @client.protocol = "pbc"
111
+ old.should_not eq(@client.backend)
112
+ end
113
+ end
114
+
115
+ describe "setting the host" do
116
+ it "should allow setting the host" do
117
+ @client.should respond_to(:host=)
118
+ @client.host = "riak.basho.com"
119
+ @client.host.should == "riak.basho.com"
120
+ end
121
+
122
+ it "should require the host to be an IP or hostname" do
123
+ [238472384972, ""].each do |invalid|
124
+ lambda { @client.host = invalid }.should raise_error(ArgumentError)
125
+ end
126
+ ["127.0.0.1", "10.0.100.5", "localhost", "otherhost.local", "riak.basho.com"].each do |valid|
127
+ lambda { @client.host = valid }.should_not raise_error
128
+ end
129
+ end
130
+ end
131
+
132
+ [:http, :pb].each do |protocol|
133
+ describe "setting the #{protocol} port" do
134
+ it "should allow setting the #{protocol} port" do
135
+ @client.should respond_to("#{protocol}_port=")
136
+ @client.send("#{protocol}_port=", 9000)
137
+ @client.send("#{protocol}_port").should == 9000
138
+ end
139
+
140
+ it "should require the port to be a valid number" do
141
+ [-1,65536,"foo"].each do |invalid|
142
+ lambda { @client.send("#{protocol}_port=",invalid) }.should raise_error(ArgumentError)
143
+ end
144
+ [0,1,65535,8098].each do |valid|
145
+ lambda { @client.send("#{protocol}_port=", valid) }.should_not raise_error
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "setting the port" do
152
+ before do
153
+ @client.stub!(:warn).and_return(true)
154
+ end
155
+ it "should raise a deprecation warning" do
156
+ @client.should_receive(:warn).and_return(true)
157
+ @client.port = 9000
158
+ end
159
+
160
+ it "should set the port for the selected protocol" do
161
+ @client.protocol = "pbc"
162
+ @client.port = 9000
163
+ @client.pb_port.should == 9000
164
+ end
165
+ end
166
+
167
+ describe "setting http auth" do
168
+ it "should allow setting basic_auth" do
169
+ @client.should respond_to(:basic_auth=)
170
+ @client.basic_auth = "user:pass"
171
+ @client.basic_auth.should eq("user:pass")
172
+ end
173
+
174
+ it "should require that basic auth splits into two even parts" do
175
+ lambda { @client.basic_auth ="user" }.should raise_error(ArgumentError, "basic auth must be set using 'user:pass'")
176
+ end
177
+ end
178
+
179
+ it "should allow setting the prefix" do
180
+ @client.should respond_to(:prefix=)
181
+ @client.prefix = "/another-prefix"
182
+ @client.prefix.should == "/another-prefix"
183
+ end
184
+
185
+ describe "setting the client id" do
186
+ it "should accept a string unmodified" do
187
+ @client.client_id = "foo"
188
+ @client.client_id.should == "foo"
189
+ end
190
+
191
+ it "should reject an integer equal to the maximum client id" do
192
+ lambda { @client.client_id = Riak::Client::MAX_CLIENT_ID }.should raise_error(ArgumentError)
193
+ end
194
+
195
+ it "should reject an integer larger than the maximum client id" do
196
+ lambda { @client.client_id = Riak::Client::MAX_CLIENT_ID + 1 }.should raise_error(ArgumentError)
197
+ end
198
+ end
199
+ end
200
+
201
+ describe "choosing an HTTP backend" do
202
+ before :each do
203
+ @client = Riak::Client.new
204
+ end
205
+
206
+ it "should choose the selected backend" do
207
+ @client.http_backend = :NetHTTP
208
+ @client.http.should be_instance_of(Riak::Client::NetHTTPBackend)
209
+
210
+ @client = Riak::Client.new
211
+ @client.http_backend = :Excon
212
+ @client.http.should be_instance_of(Riak::Client::ExconBackend)
213
+ end
214
+
215
+ it "should raise an error when the chosen backend is not valid" do
216
+ Riak::Client::NetHTTPBackend.should_receive(:configured?).and_return(false)
217
+ lambda { @client.http }.should raise_error
218
+ end
219
+ end
220
+
221
+ describe "choosing a Protobuffs backend" do
222
+ before :each do
223
+ @client = Riak::Client.new(:protocol => "pbc")
224
+ end
225
+
226
+ it "should choose the selected backend" do
227
+ @client.protobuffs_backend = :Beefcake
228
+ @client.protobuffs.should be_instance_of(Riak::Client::BeefcakeProtobuffsBackend)
229
+ end
230
+
231
+ it "should raise an error when the chosen backend is not valid" do
232
+ Riak::Client::BeefcakeProtobuffsBackend.should_receive(:configured?).and_return(false)
233
+ lambda { @client.protobuffs }.should raise_error
234
+ end
235
+ end
236
+
237
+ describe "choosing a unified backend" do
238
+ before :each do
239
+ @client = Riak::Client.new
240
+ end
241
+
242
+ it "should use HTTP when the protocol is http or https" do
243
+ %w[http https].each do |p|
244
+ @client.protocol = p
245
+ @client.backend.should be_kind_of(Riak::Client::HTTPBackend)
246
+ end
247
+ end
248
+
249
+ it "should use Protobuffs when the protocol is pbc" do
250
+ @client.protocol = "pbc"
251
+ @client.backend.should be_kind_of(Riak::Client::ProtobuffsBackend)
252
+ end
253
+ end
254
+
255
+ describe "retrieving a bucket" do
256
+ before :each do
257
+ @client = Riak::Client.new
258
+ @backend = mock("Backend")
259
+ @client.stub!(:backend).and_return(@backend)
260
+ end
261
+
262
+ it "should return a bucket object" do
263
+ @client.bucket("foo").should be_kind_of(Riak::Bucket)
264
+ end
265
+
266
+ it "should fetch bucket properties if asked" do
267
+ @backend.should_receive(:get_bucket_props) {|b| b.name.should == "foo"; {} }
268
+ @client.bucket("foo", :props => true)
269
+ end
270
+
271
+ it "should memoize bucket parameters" do
272
+ @bucket = mock("Bucket")
273
+ Riak::Bucket.should_receive(:new).with(@client, "baz").once.and_return(@bucket)
274
+ @client.bucket("baz").should == @bucket
275
+ @client.bucket("baz").should == @bucket
276
+ end
277
+ end
278
+
279
+ describe "listing buckets" do
280
+ before do
281
+ @client = Riak::Client.new
282
+ @backend = mock("Backend")
283
+ @client.stub!(:backend).and_return(@backend)
284
+ end
285
+
286
+ after { Riak.disable_list_keys_warnings = true }
287
+
288
+ it "should list buckets" do
289
+ @backend.should_receive(:list_buckets).and_return(%w{test test2})
290
+ buckets = @client.buckets
291
+ buckets.should have(2).items
292
+ buckets.should be_all {|b| b.is_a?(Riak::Bucket) }
293
+ buckets[0].name.should == "test"
294
+ buckets[1].name.should == "test2"
295
+ end
296
+
297
+ it "should warn about the expense of list-buckets when warnings are not disabled" do
298
+ Riak.disable_list_keys_warnings = false
299
+ @backend.stub!(:list_buckets).and_return(%w{test test2})
300
+ @client.should_receive(:warn)
301
+ @client.buckets
302
+ end
303
+ end
304
+
305
+ describe "Luwak (large-files) support" do
306
+ describe "storing a file" do
307
+ before :each do
308
+ @client = Riak::Client.new
309
+ @http = mock(Riak::Client::HTTPBackend)
310
+ @client.stub!(:http).and_return(@http)
311
+ end
312
+
313
+ it "should store the file in Luwak and return the key/filename when no filename is given" do
314
+ @http.should_receive(:post).with(201, "/luwak", anything, {"Content-Type" => "text/plain"}).and_return(:code => 201, :headers => {"location" => ["/luwak/123456789"]})
315
+ @client.store_file("text/plain", "Hello, world").should == "123456789"
316
+ end
317
+
318
+ it "should store the file in Luwak and return the key/filename when the filename is given" do
319
+ @http.should_receive(:put).with(204, "/luwak", "greeting.txt", anything, {"Content-Type" => "text/plain"}).and_return(:code => 204, :headers => {})
320
+ @client.store_file("greeting.txt", "text/plain", "Hello, world").should == "greeting.txt"
321
+ end
322
+ end
323
+
324
+ describe "retrieving a file" do
325
+ before :each do
326
+ @client = Riak::Client.new
327
+ @http = mock(Riak::Client::HTTPBackend)
328
+ @client.stub!(:http).and_return(@http)
329
+ @http.should_receive(:get).with(200, "/luwak", "greeting.txt").and_yield("Hello,").and_yield(" world!").and_return({:code => 200, :headers => {"content-type" => ["text/plain"]}})
330
+ end
331
+
332
+ it "should stream the data to a temporary file" do
333
+ file = @client.get_file("greeting.txt")
334
+ file.open {|f| f.read.should == "Hello, world!" }
335
+ end
336
+
337
+ it "should stream the data through the given block, returning nil" do
338
+ string = ""
339
+ result = @client.get_file("greeting.txt"){|chunk| string << chunk }
340
+ result.should be_nil
341
+ string.should == "Hello, world!"
342
+ end
343
+
344
+ it "should expose the original key and content-type on the temporary file" do
345
+ file = @client.get_file("greeting.txt")
346
+ file.content_type.should == "text/plain"
347
+ file.original_filename.should == "greeting.txt"
348
+ end
349
+ end
350
+
351
+ it "should delete a file" do
352
+ @client = Riak::Client.new
353
+ @http = mock(Riak::Client::HTTPBackend)
354
+ @client.stub!(:http).and_return(@http)
355
+ @http.should_receive(:delete).with([204,404], "/luwak", "greeting.txt")
356
+ @client.delete_file("greeting.txt")
357
+ end
358
+
359
+ it "should return true if the file exists" do
360
+ @client = Riak::Client.new
361
+ @client.http.should_receive(:head).and_return(:code => 200)
362
+ @client.file_exists?("foo").should be_true
363
+ end
364
+
365
+ it "should return false if the file doesn't exist" do
366
+ @client = Riak::Client.new
367
+ @client.http.should_receive(:head).and_return(:code => 404)
368
+ @client.file_exists?("foo").should be_false
369
+ end
370
+
371
+ it "should escape the filename when storing, retrieving or deleting files" do
372
+ @client = Riak::Client.new
373
+ @http = mock(Riak::Client::HTTPBackend)
374
+ @client.stub!(:http).and_return(@http)
375
+ # Delete escapes keys
376
+ @http.should_receive(:delete).with([204,404], "/luwak", "docs%2FA%20Big%20PDF.pdf")
377
+ @client.delete_file("docs/A Big PDF.pdf")
378
+ # Get escapes keys
379
+ @http.should_receive(:get).with(200, "/luwak", "docs%2FA%20Big%20PDF.pdf").and_yield("foo").and_return(:headers => {"content-type" => ["text/plain"]}, :code => 200)
380
+ @client.get_file("docs/A Big PDF.pdf")
381
+ # Streamed get escapes keys
382
+ @http.should_receive(:get).with(200, "/luwak", "docs%2FA%20Big%20PDF.pdf").and_yield("foo").and_return(:headers => {"content-type" => ["text/plain"]}, :code => 200)
383
+ @client.get_file("docs/A Big PDF.pdf"){|chunk| chunk}
384
+ # Put escapes keys
385
+ @http.should_receive(:put).with(204, "/luwak", "docs%2FA%20Big%20PDF.pdf", "foo", {"Content-Type" => "text/plain"})
386
+ @client.store_file("docs/A Big PDF.pdf", "text/plain", "foo")
387
+ end
388
+ end
389
+
390
+ describe "ssl", :ssl => true do
391
+ before :each do
392
+ @client = Riak::Client.new
393
+ end
394
+
395
+ it "should allow passing ssl options into the initializer" do
396
+ lambda { client = Riak::Client.new(:ssl => {:verify_mode => "peer"}) }.should_not raise_error
397
+ end
398
+
399
+ it "should not have ssl options by default" do
400
+ @client.ssl_options.should be_nil
401
+ end
402
+
403
+ it "should have a blank hash for ssl options if the protocol is set to https" do
404
+ @client.protocol = 'https'
405
+ @client.ssl_options.should be_a(Hash)
406
+ end
407
+
408
+ # The api should have an ssl= method for setting up all of the ssl
409
+ # options. Once the ssl options have been assigned via `ssl=` they should
410
+ # be read back out using the read only `ssl_options`. This is to provide
411
+ # a seperate api for setting ssl options via client configuration and
412
+ # reading them inside of a http backend.
413
+ it "should not allow reading ssl options via ssl" do
414
+ @client.should_not respond_to(:ssl)
415
+ end
416
+
417
+ it "should now allow writing ssl options via ssl_options=" do
418
+ @client.should_not respond_to(:ssl_options=)
419
+ end
420
+
421
+ it "should allow setting ssl to true" do
422
+ @client.ssl = true
423
+ @client.ssl_options[:verify_mode].should eq('none')
424
+ end
425
+
426
+ it "should allow setting ssl options as a hash" do
427
+ @client.ssl = {:verify_mode => "peer"}
428
+ @client.ssl_options[:verify_mode].should eq('peer')
429
+ end
430
+
431
+ it "should set the protocol to https when setting ssl to true" do
432
+ @client.ssl = true
433
+ @client.protocol.should eq("https")
434
+ end
435
+
436
+ it "should set the protocol to http when setting ssl to false" do
437
+ @client.protocol = 'https'
438
+ @client.ssl = false
439
+ @client.protocol.should eq('http')
440
+ end
441
+
442
+ it "should should clear ssl options when setting ssl to false" do
443
+ @client.ssl = true
444
+ @client.ssl_options.should_not be_nil
445
+ @client.ssl = false
446
+ @client.ssl_options.should be_nil
447
+ end
448
+
449
+ it "should set the protocol to https when setting ssl options" do
450
+ @client.ssl = {:verify_mode => "peer"}
451
+ @client.protocol.should eq("https")
452
+ end
453
+
454
+ it "should allow setting the verify_mode to none" do
455
+ @client.ssl = {:verify_mode => "none"}
456
+ @client.ssl_options[:verify_mode].should eq("none")
457
+ end
458
+
459
+ it "should allow setting the verify_mode to peer" do
460
+ @client.ssl = {:verify_mode => "peer"}
461
+ @client.ssl_options[:verify_mode].should eq("peer")
462
+ end
463
+
464
+ it "should not allow setting the verify_mode to anything else" do
465
+ lambda {@client.ssl = {:verify_mode => :your_mom}}.should raise_error(ArgumentError)
466
+ end
467
+
468
+ it "should default verify_mode to none" do
469
+ @client.ssl = true
470
+ @client.ssl_options[:verify_mode].should eq("none")
471
+ end
472
+
473
+ it "should let the backend know if ssl is enabled" do
474
+ @client.should_not be_ssl_enabled
475
+ @client.ssl = true
476
+ @client.should be_ssl_enabled
477
+ end
478
+
479
+ it "should allow setting the pem" do
480
+ @client.ssl = {:pem => 'i-am-a-pem'}
481
+ @client.ssl_options[:pem].should eq('i-am-a-pem')
482
+ end
483
+
484
+ it "should set them pem from the contents of pem_file" do
485
+ filepath = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test.pem'))
486
+ @client.ssl = {:pem_file => filepath}
487
+ @client.ssl_options[:pem].should eq("i-am-a-pem\n")
488
+ end
489
+
490
+ it "should allow setting the pem_password" do
491
+ @client.ssl = {:pem_password => 'pem-pass'}
492
+ @client.ssl_options[:pem_password].should eq('pem-pass')
493
+ end
494
+
495
+ it "should allow setting the ca_file" do
496
+ @client.ssl = {:ca_file => '/path/to/ca.crt'}
497
+ @client.ssl_options[:ca_file].should eq('/path/to/ca.crt')
498
+ end
499
+
500
+ it "should allow setting the ca_path" do
501
+ @client.ssl = {:ca_path => '/path/to/certs/'}
502
+ @client.ssl_options[:ca_path].should eq('/path/to/certs/')
503
+ end
504
+
505
+ %w[pem ca_file ca_path].each do |option|
506
+ it "should default the verify_mode to peer when setting the #{option}" do
507
+ @client.ssl = {option.to_sym => 'test-data'}
508
+ @client.ssl_options[:verify_mode].should eq("peer")
509
+ end
510
+
511
+ it "should allow setting the verify mode when setting the #{option}" do
512
+ @client.ssl = {option.to_sym => 'test-data', :verify_mode => "none"}
513
+ @client.ssl_options[:verify_mode].should eq("none")
514
+ end
515
+ end
516
+ end
517
+ end