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,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Search features" do
4
+ describe Riak::Client do
5
+ before :each do
6
+ @client = Riak::Client.new
7
+ @http = mock(Riak::Client::HTTPBackend)
8
+ @client.stub!(:http).and_return(@http)
9
+ end
10
+
11
+ describe "searching" do
12
+ it "should exclude the index from the URL when not specified" do
13
+ @http.should_receive(:get).with(200, "/solr", "select", hash_including("q" => "foo"), {}).and_return({:code => 200, :headers => {"content-type"=>["application/json"]}, :body => "{}"})
14
+ @client.search("foo")
15
+ end
16
+
17
+ it "should include extra options in the query string" do
18
+ @http.should_receive(:get).with(200, "/solr", "select", hash_including('rows' => 30), {}).and_return({:code => 200, :headers => {"content-type"=>["application/json"]}, :body => "{}"})
19
+ @client.search("foo", 'rows' => 30)
20
+ end
21
+
22
+ it "should include the index in the URL when specified" do
23
+ @http.should_receive(:get).with(200, "/solr", "search", "select", hash_including("q" => "foo"), {}).and_return({:code => 200, :headers => {"content-type"=>["application/json"]}, :body => "{}"})
24
+ @client.search("search", "foo")
25
+ end
26
+
27
+ it "should vivify JSON responses" do
28
+ @http.should_receive(:get).and_return({:code => 200, :headers => {"content-type"=>["application/json"]}, :body => '{"response":{"docs":["foo"]}}'})
29
+ @client.search("foo").should == {"response" => {"docs" => ["foo"]}}
30
+ end
31
+
32
+ it "should return non-JSON responses raw" do
33
+ @http.should_receive(:get).and_return({:code => 200, :headers => {"content-type"=>["text/plain"]}, :body => '{"response":{"docs":["foo"]}}'})
34
+ @client.search("foo").should == '{"response":{"docs":["foo"]}}'
35
+ end
36
+ end
37
+ describe "indexing documents" do
38
+ it "should exclude the index from the URL when not specified" do
39
+ @http.should_receive(:post).with(200, "/solr", "update", anything, anything).and_return({:code => 200, :headers => {'content-type' => ['text/html']}, :body => ""})
40
+ @client.index({:id => 1, :field => "value"})
41
+ end
42
+
43
+ it "should include the index in the URL when specified" do
44
+ @http.should_receive(:post).with(200, "/solr", "foo", "update", anything, anything).and_return({:code => 200, :headers => {'content-type' => ['text/html']}, :body => ""})
45
+ @client.index("foo", {:id => 1, :field => "value"})
46
+ end
47
+
48
+ it "should raise an error when documents do not contain an id" do
49
+ @http.stub!(:post).and_return(true)
50
+ lambda { @client.index({:field => "value"}) }.should raise_error(ArgumentError)
51
+ lambda { @client.index({:id => 1, :field => "value"}) }.should_not raise_error(ArgumentError)
52
+ end
53
+
54
+ it "should build a Solr <add> request" do
55
+ expect_update_body('<add><doc><field name="id">1</field><field name="field">value</field></doc></add>')
56
+ @client.index({'id' => 1, 'field' => "value"})
57
+ end
58
+
59
+ it "should include multiple documents in the <add> request" do
60
+ expect_update_body('<add><doc><field name="id">1</field><field name="field">value</field></doc><doc><field name="id">2</field><field name="foo">bar</field></doc></add>')
61
+ @client.index({'id' => 1, 'field' => "value"}, {'id' => 2, 'foo' => "bar"})
62
+ end
63
+ end
64
+
65
+ describe "removing documents" do
66
+ it "should exclude the index from the URL when not specified" do
67
+ @http.should_receive(:post).with(200, "/solr","update", anything, anything).and_return({:code => 200, :headers => {'content-type' => ['text/html']}, :body => ""})
68
+ @client.remove({:id => 1})
69
+ end
70
+
71
+ it "should include the index in the URL when specified" do
72
+ @http.should_receive(:post).with(200, "/solr", "foo", "update", anything, anything).and_return({:code => 200, :headers => {'content-type' => ['text/html']}, :body => ""})
73
+ @client.remove("foo", {:id => 1})
74
+ end
75
+
76
+ it "should raise an error when document specifications don't include an id or query" do
77
+ @http.stub!(:post).and_return({:code => 200})
78
+ lambda { @client.remove({:foo => "bar"}) }.should raise_error(ArgumentError)
79
+ lambda { @client.remove({:id => 1}) }.should_not raise_error
80
+ end
81
+
82
+ it "should build a Solr <delete> request" do
83
+ expect_update_body('<delete><id>1</id></delete>')
84
+ @client.remove(:id => 1)
85
+ expect_update_body('<delete><query>title:old</query></delete>')
86
+ @client.remove(:query => "title:old")
87
+ end
88
+
89
+ it "should include multiple specs in the <delete> request" do
90
+ expect_update_body('<delete><id>1</id><query>title:old</query></delete>')
91
+ @client.remove({:id => 1}, {:query => "title:old"})
92
+ end
93
+ end
94
+
95
+ def expect_update_body(body, index=nil)
96
+ args = [200, "/solr", index, "update", body, {"Content-Type" => "text/xml"}].compact
97
+ @http.should_receive(:post).with(*args).and_return({:code => 200, :headers => {'content-type' => ['text/html']}, :body => ""})
98
+ end
99
+ end
100
+
101
+ describe Riak::Bucket do
102
+ before :each do
103
+ @client = Riak::Client.new
104
+ @bucket = Riak::Bucket.new(@client, "foo")
105
+ end
106
+
107
+ def load_without_index_hook
108
+ @bucket.instance_variable_set(:@props, {"precommit" => []})
109
+ end
110
+
111
+ def load_with_index_hook
112
+ @bucket.instance_variable_set(:@props, {"precommit" => [{"mod" => "riak_search_kv_hook", "fun" => "precommit"}]})
113
+ end
114
+
115
+ it "should detect whether the indexing hook is installed" do
116
+ load_without_index_hook
117
+ @bucket.is_indexed?.should be_false
118
+
119
+ load_with_index_hook
120
+ @bucket.is_indexed?.should be_true
121
+ end
122
+
123
+ describe "enabling indexing" do
124
+ it "should add the index hook when not present" do
125
+ load_without_index_hook
126
+ @bucket.should_receive(:props=).with({"precommit" => [Riak::Bucket::SEARCH_PRECOMMIT_HOOK]})
127
+ @bucket.enable_index!
128
+ end
129
+
130
+ it "should not modify the precommit when the hook is present" do
131
+ load_with_index_hook
132
+ @bucket.should_not_receive(:props=)
133
+ @bucket.enable_index!
134
+ end
135
+ end
136
+
137
+ describe "disabling indexing" do
138
+ it "should remove the index hook when present" do
139
+ load_with_index_hook
140
+ @bucket.should_receive(:props=).with({"precommit" => []})
141
+ @bucket.disable_index!
142
+ end
143
+
144
+ it "should not modify the precommit when the hook is missing" do
145
+ load_without_index_hook
146
+ @bucket.should_not_receive(:props=)
147
+ @bucket.disable_index!
148
+ end
149
+ end
150
+ end
151
+
152
+ describe Riak::MapReduce do
153
+ before :each do
154
+ @client = Riak::Client.new
155
+ @mr = Riak::MapReduce.new(@client)
156
+ end
157
+
158
+ describe "using a search query as inputs" do
159
+ it "should accept a bucket name and query" do
160
+ @mr.search("foo", "bar OR baz")
161
+ @mr.inputs.should == {:module => "riak_search", :function => "mapred_search", :arg => ["foo", "bar OR baz"]}
162
+ end
163
+
164
+ it "should accept a Riak::Bucket and query" do
165
+ @mr.search(Riak::Bucket.new(@client, "foo"), "bar OR baz")
166
+ @mr.inputs.should == {:module => "riak_search", :function => "mapred_search", :arg => ["foo", "bar OR baz"]}
167
+ end
168
+
169
+ it "should emit the Erlang function and arguments" do
170
+ @mr.search("foo", "bar OR baz")
171
+ @mr.to_json.should include('"inputs":{')
172
+ @mr.to_json.should include('"module":"riak_search"')
173
+ @mr.to_json.should include('"function":"mapred_search"')
174
+ @mr.to_json.should include('"arg":["foo","bar OR baz"]')
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Serializers do
4
+ shared_examples_for "a serializer" do |type, deserialized, serialized|
5
+ context "for #{type}" do
6
+ it "serializes #{deserialized} to #{serialized}" do
7
+ described_class.serialize(type, deserialized).should == serialized
8
+ end
9
+
10
+ it "deserializes #{serialized} to #{deserialized}" do
11
+ described_class.deserialize(type, serialized).should == deserialized
12
+ end
13
+
14
+ it "round trips properly" do
15
+ str = described_class.serialize(type, deserialized)
16
+ described_class.deserialize(type, str).should == deserialized
17
+ end
18
+ end
19
+ end
20
+
21
+ it_behaves_like "a serializer", "text/plain", "a string", "a string"
22
+ it_behaves_like "a serializer", "application/json", { "a" => 7 }, %q|{"a":7}|
23
+ it_behaves_like "a serializer", "application/x-ruby-marshal", { :a => 3 }, Marshal.dump({ :a => 3 })
24
+
25
+ described_class::YAML_MIME_TYPES.each do |mime_type|
26
+ it_behaves_like "a serializer", mime_type, { "a" => 7 }, YAML.dump({ "a" => 7 })
27
+ end
28
+
29
+ %w[ serialize deserialize ].each do |meth|
30
+ describe ".#{meth}" do
31
+ it 'raises a NotImplementedError when given an unrecognized content type' do
32
+ expect {
33
+ described_class.send(meth, "application/unrecognized", "string")
34
+ }.to raise_error(NotImplementedError)
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "plain text serializer" do
40
+ it 'calls #to_s to convert the object to a string' do
41
+ described_class.serialize("text/plain", :a_string).should == "a_string"
42
+ end
43
+ end
44
+
45
+ describe "JSON serializer" do
46
+ it "respects the max nesting option" do
47
+ # Sadly, this spec will not fail for me when using yajl-ruby
48
+ # on Ruby 1.9, even when passing the options to #to_json is
49
+ # not implemented.
50
+ Riak.json_options = {:max_nesting => 51}
51
+ h = {}
52
+ p = h
53
+ (1..50).each do |i|
54
+ p['a'] = {}
55
+ p = p['a']
56
+ end
57
+ s = h.to_json(Riak.json_options)
58
+ expect {
59
+ described_class.serialize('application/json', h)
60
+ }.should_not raise_error
61
+
62
+ expect {
63
+ described_class.deserialize('application/json', s)
64
+ }.should_not raise_error
65
+ end
66
+ end
67
+
68
+ describe "a custom serializer" do
69
+ let(:custom_serializer) do
70
+ Object.new.tap do |o|
71
+ def o.dump(string)
72
+ "The string is: #{string}"
73
+ end
74
+
75
+ def o.load(string)
76
+ string.sub!(/^The string is: /, '')
77
+ end
78
+ end
79
+ end
80
+
81
+ it 'can be registered' do
82
+ described_class['application/custom-type-1'] = custom_serializer
83
+ described_class['application/custom-type-1'].should be(custom_serializer)
84
+ end
85
+
86
+ it_behaves_like "a serializer", "application/custom-type-a", "foo", "The string is: foo" do
87
+ before(:each) do
88
+ described_class['application/custom-type-a'] = custom_serializer
89
+ end
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'riak/stamp'
3
+
4
+ describe Riak::Stamp do
5
+ subject { described_class.new(Riak::Client.new) }
6
+ it "should generate always increasing integer identifiers" do
7
+ 1000.times do
8
+ one = subject.next
9
+ two = subject.next
10
+ [one, two].should be_all {|i| Integer === i }
11
+ two.should > one
12
+ end
13
+ end
14
+
15
+ it "should delay until the next millisecond when the sequence overflows" do
16
+ old = subject.instance_variable_get(:@timestamp) + 0
17
+ subject.instance_variable_set(:@sequence, described_class::SEQUENCE_MASK)
18
+ count = 0
19
+ # Simulate the time_gen method returning the same thing multiple times
20
+ subject.stub(:time_gen) do
21
+ count += 1
22
+ if count < 10
23
+ old
24
+ else
25
+ old + 1
26
+ end
27
+ end
28
+ ((subject.next >> described_class::TIMESTAMP_SHIFT) & described_class::TIMESTAMP_MASK).should == old + 1
29
+ end
30
+
31
+ it "should raise an exception when the system clock moves backwards" do
32
+ old = subject.instance_variable_get(:@timestamp)
33
+ subject.should_receive(:time_gen).and_return(old - 10)
34
+ expect {
35
+ subject.next
36
+ }.to raise_error(Riak::BackwardsClockError)
37
+ end
38
+
39
+ # The client/worker ID should be used for disambiguation, not for
40
+ # primary ordering. This breaks from the Snowflake model where the
41
+ # worker ID is in more significant bits.
42
+ it "should use the client ID as the bottom component of the identifier" do
43
+ (subject.next & described_class::CLIENT_ID_MASK).should == subject.client.client_id & described_class::CLIENT_ID_MASK
44
+ end
45
+
46
+ context "using a non-integer client ID" do
47
+ subject { described_class.new(Riak::Client.new(:client_id => "ripple")) }
48
+ let(:hash) { "ripple".hash }
49
+
50
+ it "should use the hash of the client ID as the bottom component of the identifier" do
51
+ (subject.next & described_class::CLIENT_ID_MASK).should == subject.client.client_id.hash & described_class::CLIENT_ID_MASK
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Util::Multipart::StreamParser do
4
+ let(:klass) { Riak::Util::Multipart::StreamParser }
5
+ let(:block) { mock }
6
+ it "should detect the initial boundary" do
7
+ text = "--boundary1\r\nContent-Type: text/plain\r\n\r\nfoo\r\n--boundary1--\r\n"
8
+ parser = klass.new do |result|
9
+ result[:headers]['content-type'].should include("text/plain")
10
+ result[:body].should == "foo"
11
+ end
12
+ parser.accept text
13
+ end
14
+
15
+ it "should detect inner multipart bodies" do
16
+ block.should_receive(:ping).once.and_return(true)
17
+ parser = klass.new do |result|
18
+ block.ping
19
+ result.should have(1).item
20
+ result.first[:headers]['content-type'].should include("text/plain")
21
+ result.first[:body].should == "SCP sloooow...."
22
+ end
23
+ File.open("spec/fixtures/multipart-with-body.txt", "r") do |f|
24
+ while chunk = f.read(16)
25
+ parser.accept chunk
26
+ end
27
+ end
28
+ end
29
+
30
+ it "should yield successive complete chunks to the block" do
31
+ block.should_receive(:ping).twice.and_return(true)
32
+ parser = klass.new do |result|
33
+ block.ping
34
+ result[:headers]['content-type'].should include("application/json")
35
+ lambda { Riak::JSON.parse(result[:body]) }.should_not raise_error
36
+ end
37
+ File.open("spec/fixtures/multipart-mapreduce.txt", "r") do |f|
38
+ while chunk = f.read(16)
39
+ parser.accept chunk
40
+ end
41
+ end
42
+ end
43
+
44
+ it "should yield successive complete bodies to the block, even when multiple bodies are accepted in a single chunk" do
45
+ block.should_receive(:ping).twice.and_return(true)
46
+ parser = klass.new do |result|
47
+ block.ping
48
+ result[:headers]['content-type'].should include("application/json")
49
+ lambda { Riak::JSON.parse(result[:body]) }.should_not raise_error
50
+ end
51
+ parser.accept File.read("spec/fixtures/multipart-mapreduce.txt")
52
+ end
53
+ end
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::WalkSpec do
4
+ describe "initializing" do
5
+ describe "with a hash" do
6
+ it "should be empty by default" do
7
+ spec = Riak::WalkSpec.new({})
8
+ spec.bucket.should == "_"
9
+ spec.tag.should == "_"
10
+ spec.keep.should be_false
11
+ end
12
+
13
+ it "should extract the bucket" do
14
+ spec = Riak::WalkSpec.new({:bucket => "foo"})
15
+ spec.bucket.should == "foo"
16
+ spec.tag.should == "_"
17
+ spec.keep.should be_false
18
+ end
19
+
20
+ it "should extract the tag" do
21
+ spec = Riak::WalkSpec.new({:tag => "foo"})
22
+ spec.bucket.should == "_"
23
+ spec.tag.should == "foo"
24
+ spec.keep.should be_false
25
+ end
26
+
27
+ it "should extract the keep" do
28
+ spec = Riak::WalkSpec.new({:keep => true})
29
+ spec.bucket.should == "_"
30
+ spec.tag.should == "_"
31
+ spec.keep.should be_true
32
+ end
33
+ end
34
+
35
+ describe "with three arguments for bucket, tag, and keep" do
36
+ it "should assign the bucket, tag, and keep" do
37
+ spec = Riak::WalkSpec.new("foo", "next", false)
38
+ spec.bucket.should == "foo"
39
+ spec.tag.should == "next"
40
+ spec.keep.should be_false
41
+ end
42
+
43
+ it "should make the bucket '_' when false or nil" do
44
+ spec = Riak::WalkSpec.new(nil, "next", false)
45
+ spec.bucket.should == "_"
46
+ spec = Riak::WalkSpec.new(false, "next", false)
47
+ spec.bucket.should == "_"
48
+ end
49
+
50
+ it "should make the tag '_' when false or nil" do
51
+ spec = Riak::WalkSpec.new("foo", nil, false)
52
+ spec.tag.should == "_"
53
+ spec = Riak::WalkSpec.new("foo", false, false)
54
+ spec.tag.should == "_"
55
+ end
56
+
57
+ it "should make the keep false when false or nil" do
58
+ spec = Riak::WalkSpec.new(nil, nil, nil)
59
+ spec.keep.should be_false
60
+ spec = Riak::WalkSpec.new(nil, nil, false)
61
+ spec.keep.should be_false
62
+ end
63
+ end
64
+
65
+ it "should raise an ArgumentError for invalid arguments" do
66
+ lambda { Riak::WalkSpec.new }.should raise_error(ArgumentError)
67
+ lambda { Riak::WalkSpec.new("foo") }.should raise_error(ArgumentError)
68
+ lambda { Riak::WalkSpec.new("foo","bar") }.should raise_error(ArgumentError)
69
+ end
70
+ end
71
+
72
+ describe "converting to a string" do
73
+ before :each do
74
+ @spec = Riak::WalkSpec.new({})
75
+ end
76
+
77
+ it "should be the empty spec by default" do
78
+ @spec.to_s.should == "_,_,_"
79
+ end
80
+
81
+ it "should include the bucket when set" do
82
+ @spec.bucket = "foo"
83
+ @spec.to_s.should == "foo,_,_"
84
+ end
85
+
86
+ it "should include the tag when set" do
87
+ @spec.tag = "next"
88
+ @spec.to_s.should == "_,next,_"
89
+ end
90
+
91
+ it "should include the keep when true" do
92
+ @spec.keep = true
93
+ @spec.to_s.should == "_,_,1"
94
+ end
95
+ end
96
+
97
+ describe "creating from a list of parameters" do
98
+ it "should detect hashes and WalkSpecs interleaved with other parameters" do
99
+ specs = Riak::WalkSpec.normalize(nil,"next",nil,{:bucket => "foo"},Riak::WalkSpec.new({:tag => "child", :keep => true}))
100
+ specs.should have(3).items
101
+ specs.should be_all {|s| s.kind_of?(Riak::WalkSpec) }
102
+ specs.join("/").should == "_,next,_/foo,_,_/_,child,1"
103
+ end
104
+ end
105
+
106
+ describe "matching other objects with ===" do
107
+ before :each do
108
+ @spec = Riak::WalkSpec.new({})
109
+ end
110
+
111
+ it "should not match objects that aren't links or walk specs" do
112
+ @spec.should_not === "foo"
113
+ end
114
+
115
+ describe "matching links" do
116
+ before :each do
117
+ @link = Riak::Link.new("/riak/foo/bar", "next")
118
+ end
119
+
120
+ it "should match a link when the bucket and tag are not specified" do
121
+ @spec.should === @link
122
+ end
123
+
124
+ it "should match a link when the bucket is the same" do
125
+ @spec.bucket = "foo"
126
+ @spec.should === @link
127
+ end
128
+
129
+ it "should not match a link when the bucket is different" do
130
+ @spec.bucket = "bar"
131
+ @spec.should_not === @link
132
+ end
133
+
134
+ it "should match a link when the tag is the same" do
135
+ @spec.tag = "next"
136
+ @spec.should === @link
137
+ end
138
+
139
+ it "should not match a link when the tag is different" do
140
+ @spec.tag = "previous"
141
+ @spec.should_not === @link
142
+ end
143
+
144
+ it "should match a link when the bucket and tag are the same" do
145
+ @spec.bucket = "foo"
146
+ @spec.should === @link
147
+ end
148
+ end
149
+
150
+ describe "matching walk specs" do
151
+ before :each do
152
+ @other = Riak::WalkSpec.new({})
153
+ end
154
+
155
+ it "should match a walk spec that is equivalent" do
156
+ @spec.should === @other
157
+ end
158
+
159
+ it "should not match a walk spec that has a different keep value" do
160
+ @other.keep = true
161
+ @spec.should_not === @other
162
+ end
163
+
164
+ it "should match a walk spec with a more specific bucket" do
165
+ @other.bucket = "foo"
166
+ @spec.should === @other
167
+ end
168
+
169
+ it "should match a walk spec with the same bucket" do
170
+ @other.bucket = "foo"; @spec.bucket = "foo"
171
+ @spec.should === @other
172
+ end
173
+
174
+ it "should not match a walk spec with a different bucket" do
175
+ @other.bucket = "foo"; @spec.bucket = "bar"
176
+ @spec.should_not === @other
177
+ end
178
+
179
+ it "should match a walk spec with a more specific tag" do
180
+ @other.tag = "next"
181
+ @spec.should === @other
182
+ end
183
+
184
+ it "should match a walk spec with the same tag" do
185
+ @other.tag = "next"; @spec.tag = "next"
186
+ @spec.should === @other
187
+ end
188
+
189
+ it "should not match a walk spec with a different tag" do
190
+ @other.tag = "next"; @spec.tag = "previous"
191
+ @spec.should_not === @other
192
+ end
193
+ end
194
+ end
195
+ end