rsolr 0.12.0 → 2.6.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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +29 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/CHANGES.txt +63 -260
- data/Gemfile +13 -0
- data/README.rdoc +177 -63
- data/Rakefile +19 -0
- data/lib/rsolr/char.rb +6 -0
- data/lib/rsolr/client.rb +344 -86
- data/lib/rsolr/document.rb +66 -0
- data/lib/rsolr/error.rb +182 -0
- data/lib/rsolr/field.rb +87 -0
- data/lib/rsolr/generator.rb +5 -0
- data/lib/rsolr/json.rb +60 -0
- data/lib/rsolr/response.rb +95 -0
- data/lib/rsolr/uri.rb +25 -0
- data/lib/rsolr/version.rb +7 -0
- data/lib/rsolr/xml.rb +150 -0
- data/lib/rsolr.rb +47 -35
- data/rsolr.gemspec +44 -31
- data/spec/api/client_spec.rb +423 -0
- data/spec/api/document_spec.rb +48 -0
- data/spec/api/error_spec.rb +158 -0
- data/spec/api/json_spec.rb +248 -0
- data/spec/api/pagination_spec.rb +31 -0
- data/spec/api/rsolr_spec.rb +31 -0
- data/spec/api/uri_spec.rb +37 -0
- data/spec/api/xml_spec.rb +255 -0
- data/spec/fixtures/basic_configs/_rest_managed.json +1 -0
- data/spec/fixtures/basic_configs/currency.xml +67 -0
- data/spec/fixtures/basic_configs/lang/stopwords_en.txt +54 -0
- data/spec/fixtures/basic_configs/protwords.txt +21 -0
- data/spec/fixtures/basic_configs/schema.xml +530 -0
- data/spec/fixtures/basic_configs/solrconfig.xml +572 -0
- data/spec/fixtures/basic_configs/stopwords.txt +14 -0
- data/spec/fixtures/basic_configs/synonyms.txt +29 -0
- data/spec/integration/solr5_spec.rb +38 -0
- data/spec/lib/rsolr/client_spec.rb +19 -0
- data/spec/spec_helper.rb +94 -0
- metadata +228 -54
- data/lib/rsolr/connection/net_http.rb +0 -48
- data/lib/rsolr/connection/requestable.rb +0 -43
- data/lib/rsolr/connection/utils.rb +0 -73
- data/lib/rsolr/connection.rb +0 -9
- data/lib/rsolr/message/document.rb +0 -48
- data/lib/rsolr/message/field.rb +0 -20
- data/lib/rsolr/message/generator.rb +0 -89
- data/lib/rsolr/message.rb +0 -8
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Error do
|
4
|
+
def generate_error_with_backtrace(request, response)
|
5
|
+
raise RSolr::Error::Http.new request, response
|
6
|
+
rescue RSolr::Error::Http => exception
|
7
|
+
exception
|
8
|
+
end
|
9
|
+
let (:response_lines) { (1..15).to_a.map { |i| "line #{i}" } }
|
10
|
+
let(:request) { { uri: URI.parse('http://hostname.local:8983/solr/admin/update?wt=json&q=test') } }
|
11
|
+
let(:response_body) { response_lines.join("\n") }
|
12
|
+
let(:response) {{
|
13
|
+
:body => response_body,
|
14
|
+
:status => 400
|
15
|
+
}}
|
16
|
+
subject { generate_error_with_backtrace(request, response).to_s }
|
17
|
+
|
18
|
+
context "when the response body is wrapped in a <pre> element" do
|
19
|
+
let(:response_body) { "<pre>" + response_lines.join("\n") + "</pre>" }
|
20
|
+
|
21
|
+
it "only shows the first eleven lines of the response" do
|
22
|
+
expect(subject).to match(/line 1\n.+line 11\n\n/m)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when the response is one line long" do
|
26
|
+
let(:response_body) { "<pre>failed</pre>" }
|
27
|
+
it { should match(/Error: failed/) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when the response body is not wrapped in a <pre> element" do
|
32
|
+
|
33
|
+
it "only shows the first eleven lines of the response" do
|
34
|
+
expect(subject).to match(/line 1\n.+line 11\n\n/m)
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the response is one line long" do
|
38
|
+
let(:response_body) { 'failed' }
|
39
|
+
it { should match(/Error: failed/) }
|
40
|
+
end
|
41
|
+
context "when the response body contains a msg key" do
|
42
|
+
let(:msg) { "'org.apache.solr.search.SyntaxError: Cannot parse \\':false\\': Encountered \" \":\" \": \"\" at line 1, column 0.'" }
|
43
|
+
let(:response_body) { (response_lines << "'error'=>{'msg'=> #{msg}").join("\n") }
|
44
|
+
it { should include msg }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the response body is made of multi-byte chars and encoded by ASCII-8bit" do
|
48
|
+
let (:response_lines) { (1..15).to_a.map { |i| "レスポンス #{i}".b } }
|
49
|
+
|
50
|
+
it "encodes errorlogs by UTF-8" do
|
51
|
+
expect(subject.encoding.to_s).to eq 'UTF-8'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when response is JSON" do
|
57
|
+
let(:response) {{
|
58
|
+
:body => response_body,
|
59
|
+
:status => 500,
|
60
|
+
:headers => {
|
61
|
+
"content-type" => "application/json;charset=utf-8"
|
62
|
+
}
|
63
|
+
|
64
|
+
}}
|
65
|
+
|
66
|
+
context "and contains a msg key" do
|
67
|
+
let(:msg) { "field 'description_text4_tesim' was indexed without offsets, cannot highlight" }
|
68
|
+
let(:response_body) {<<~EOS
|
69
|
+
{
|
70
|
+
"responseHeader":{
|
71
|
+
"status":500,
|
72
|
+
"QTime":11,
|
73
|
+
"params":{
|
74
|
+
"q":"supercali",
|
75
|
+
"hl":"true",
|
76
|
+
"hl:fl":"description_text4_tesim",
|
77
|
+
"hl.method":"unified",
|
78
|
+
"hl.offsetSource":"postings"
|
79
|
+
}
|
80
|
+
},
|
81
|
+
"response":{"numFound":0,"start":0,"maxScore":127.32743,"numFoundExact":true,"docs":[]},
|
82
|
+
"facet_counts":{
|
83
|
+
"facet_queries":{},
|
84
|
+
"facet_fields":{}
|
85
|
+
},
|
86
|
+
"error":{
|
87
|
+
"msg":"#{msg}",
|
88
|
+
"trace":"java.lang.IllegalArgumentException: field 'description_text4_tesim' was indexed without offsets, cannot highlight\\n\\tat org.apache.lucene.search.uhighlight.FieldHighlighter.highlightOffsetsEnums(FieldHighlighter.java:149)\\n\\tat org.apache.lucene.search.uhighlight.FieldHighlighter.highlightFieldForDoc(FieldHighlighter.java:79)\\n\\tat org.apache.lucene.search.uhighlight.UnifiedHighlighter.highlightFieldsAsObjects(UnifiedHighlighter.java:641)\\n\\tat org.apache.lucene.search.uhighlight.UnifiedHighlighter.highlightFields(UnifiedHighlighter.java:510)\\n\\tat org.apache.solr.highlight.UnifiedSolrHighlighter.doHighlighting(UnifiedSolrHighlighter.java:149)\\n\\tat org.apache.solr.handler.component.HighlightComponent.process(HighlightComponent.java:172)\\n\\tat org.apache.solr.handler.component.SearchHandler.handleRequestBody(SearchHandler.java:331)\\n\\tat org.apache.solr.handler.RequestHandlerBase.handleRequest(RequestHandlerBase.java:214)\\n\\tat org.apache.solr.core.SolrCore.execute(SolrCore.java:2606)\\n\\tat org.apache.solr.servlet.HttpSolrCall.execute(HttpSolrCall.java:815)\\n\\tat org.apache.solr.servlet.HttpSolrCall.call(HttpSolrCall.java:588)\\n\\tat org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:415)\\n\\tat org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:345)\\n\\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)\\n\\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\\n\\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)\\n\\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1610)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)\\n\\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1300)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)\\n\\tat org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)\\n\\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1580)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)\\n\\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1215)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\\n\\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:221)\\n\\tat org.eclipse.jetty.server.handler.InetAccessHandler.handle(InetAccessHandler.java:177)\\n\\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.server.Server.handle(Server.java:500)\\n\\tat org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)\\n\\tat org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)\\n\\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)\\n\\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)\\n\\tat org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)\\n\\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)\\n\\tat org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)\\n\\tat org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)\\n\\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)\\n\\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)\\n\\tat java.base/java.lang.Thread.run(Thread.java:834)\\n",
|
89
|
+
"code":500
|
90
|
+
}
|
91
|
+
}
|
92
|
+
EOS
|
93
|
+
}
|
94
|
+
it {
|
95
|
+
should include msg
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
context "and does not contain a msg key" do
|
100
|
+
let(:response_body) {<<~EOS
|
101
|
+
{
|
102
|
+
"responseHeader":{
|
103
|
+
"status":500,
|
104
|
+
"QTime":11,
|
105
|
+
"params":{
|
106
|
+
"q":"supercali",
|
107
|
+
"hl":"true",
|
108
|
+
"hl:fl":"description_text4_tesim",
|
109
|
+
"hl.method":"unified",
|
110
|
+
"hl.offsetSource":"postings"
|
111
|
+
}
|
112
|
+
},
|
113
|
+
"response":{"numFound":0,"start":0,"maxScore":127.32743,"numFoundExact":true,"docs":[]},
|
114
|
+
"facet_counts":{
|
115
|
+
"facet_queries":{},
|
116
|
+
"facet_fields":{}
|
117
|
+
},
|
118
|
+
}
|
119
|
+
EOS
|
120
|
+
}
|
121
|
+
it "shows the first eleven lines of the response" do
|
122
|
+
expect(subject).to include(response_body.split("\n")[0..10].join("\n"))
|
123
|
+
expect(subject).not_to include(response_body.split("\n")[11])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "and is not parseable json" do
|
128
|
+
let(:response_body) {<<~EOS
|
129
|
+
one
|
130
|
+
two
|
131
|
+
three
|
132
|
+
four
|
133
|
+
five
|
134
|
+
six
|
135
|
+
seven
|
136
|
+
eight
|
137
|
+
nine
|
138
|
+
ten
|
139
|
+
eleven
|
140
|
+
twelve
|
141
|
+
EOS
|
142
|
+
}
|
143
|
+
end
|
144
|
+
it "shows the first eleven lines of the response" do
|
145
|
+
expect(subject).to include(response_body.split("\n")[0..10].join("\n"))
|
146
|
+
expect(subject).not_to include(response_body.split("\n")[11])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when request uri contains credentials" do
|
151
|
+
let(:request) { { uri: URI.parse('http://admin:admin@hostname.local:8983/solr/admin/update?wt=json&q=test') } }
|
152
|
+
|
153
|
+
|
154
|
+
it 'includes redacted url' do
|
155
|
+
expect(subject).to include 'http://REDACTED:REDACTED@hostname.local:8983/solr/admin/update?wt=json&q=test'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::JSON do
|
4
|
+
|
5
|
+
let(:generator){ RSolr::JSON::Generator.new }
|
6
|
+
|
7
|
+
context :add do
|
8
|
+
# add a single hash ("doc")
|
9
|
+
it 'should create an add from a hash' do
|
10
|
+
data = {
|
11
|
+
:id=>"1",
|
12
|
+
:name=>'matt'
|
13
|
+
}
|
14
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
15
|
+
expect(message.length).to eq 1
|
16
|
+
expect(message.first).to eq data
|
17
|
+
end
|
18
|
+
|
19
|
+
# add an array of hashes
|
20
|
+
it 'should create many adds from an array of hashes' do
|
21
|
+
data = [
|
22
|
+
{
|
23
|
+
:id=>"1",
|
24
|
+
:name=>'matt'
|
25
|
+
},
|
26
|
+
{
|
27
|
+
:id=>"2",
|
28
|
+
:name=>'sam'
|
29
|
+
}
|
30
|
+
]
|
31
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
32
|
+
expect(message).to eq data
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should yield a Document object when #add is called with a block' do
|
36
|
+
documents = [{:id=>1, :name=>'sam', :cat=>['cat 1', 'cat 2']}]
|
37
|
+
result = generator.add(documents) do |doc|
|
38
|
+
doc.field_by_name(:name).attrs[:boost] = 10
|
39
|
+
end
|
40
|
+
|
41
|
+
message = JSON.parse(result, symbolize_names: true)
|
42
|
+
|
43
|
+
expect(message.length).to eq 1
|
44
|
+
expect(message.first).to include name: { boost: 10, value: 'sam' }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with add_attr' do
|
48
|
+
it 'should create an add command with the attributes from a hash' do
|
49
|
+
data = {
|
50
|
+
:id=>"1",
|
51
|
+
:name=>'matt'
|
52
|
+
}
|
53
|
+
message = JSON.parse(generator.add(data, boost: 1), symbolize_names: true)
|
54
|
+
expect(message).to include :add
|
55
|
+
expect(message[:add][:doc]).to eq data
|
56
|
+
expect(message[:add][:boost]).to eq 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should create multiple add command with the attributes from a hash' do
|
60
|
+
data = [
|
61
|
+
{
|
62
|
+
:id=>"1",
|
63
|
+
:name=>'matt'
|
64
|
+
},
|
65
|
+
{
|
66
|
+
:id=>"2",
|
67
|
+
:name=>'sam'
|
68
|
+
},
|
69
|
+
]
|
70
|
+
|
71
|
+
# custom JSON object class to handle Solr's non-standard JSON command format
|
72
|
+
tmp = Class.new do
|
73
|
+
def initialize
|
74
|
+
@source ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def []=(k, v)
|
78
|
+
if k == :add
|
79
|
+
@source[k] ||= []
|
80
|
+
@source[k] << v.to_h
|
81
|
+
elsif v.class == self.class
|
82
|
+
@source[k] = v.to_h
|
83
|
+
else
|
84
|
+
@source[k] = v
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_h
|
89
|
+
@source
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
request = generator.add(data, boost: 1)
|
94
|
+
message = JSON.parse(request, object_class: tmp, symbolize_names: true).to_h
|
95
|
+
expect(message[:add].length).to eq 2
|
96
|
+
expect(message[:add].map { |x| x[:doc] }).to eq data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'allows for atomic updates' do
|
101
|
+
data = {
|
102
|
+
foo: { set: 'Bar' }
|
103
|
+
}
|
104
|
+
|
105
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
106
|
+
expect(message.length).to eq 1
|
107
|
+
expect(message.first).to eq data
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'supports nested child documents' do
|
111
|
+
data = {
|
112
|
+
_childDocuments_: [
|
113
|
+
{
|
114
|
+
id: 1
|
115
|
+
},
|
116
|
+
{
|
117
|
+
id: 2
|
118
|
+
}
|
119
|
+
]
|
120
|
+
}
|
121
|
+
|
122
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
123
|
+
expect(message.length).to eq 1
|
124
|
+
expect(message.first).to eq data
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'supports nested child documents with only a single document' do
|
128
|
+
data = {
|
129
|
+
_childDocuments_: [
|
130
|
+
{
|
131
|
+
id: 1
|
132
|
+
}
|
133
|
+
]
|
134
|
+
}
|
135
|
+
|
136
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
137
|
+
expect(message.length).to eq 1
|
138
|
+
expect(message.first).to eq data
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should create multiple fields from array values' do
|
143
|
+
data = {
|
144
|
+
:id => "1",
|
145
|
+
:name => ['matt1', 'matt2']
|
146
|
+
}
|
147
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
148
|
+
expect(message.length).to eq 1
|
149
|
+
expect(message.first).to eq data
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should create multiple fields from array values with options' do
|
153
|
+
test_values = [nil, 'matt1', 'matt2']
|
154
|
+
message = JSON.parse(
|
155
|
+
generator.add(id: '1') { |doc| doc.add_field(:name, test_values, boost: 3) },
|
156
|
+
symbolize_names: true
|
157
|
+
)
|
158
|
+
expect(message).to eq [{ id: '1', name: { boost: 3, value: test_values } }]
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'for atomic updates with arrays' do
|
162
|
+
let(:test_values) { %w[value1 value2] }
|
163
|
+
|
164
|
+
it 'creates single field from array values on SET' do
|
165
|
+
expect(
|
166
|
+
JSON.parse(
|
167
|
+
generator.add(id: 'set-id') { |doc| doc.add_field(:name, test_values, update: :set) },
|
168
|
+
symbolize_names: true
|
169
|
+
)
|
170
|
+
).to eq [{ id: 'set-id', name: { set: test_values } }]
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'creates single field from array values on ADD' do
|
174
|
+
expect(
|
175
|
+
JSON.parse(
|
176
|
+
generator.add(id: 'add-id') { |doc| doc.add_field(:name, test_values, update: :add) },
|
177
|
+
symbolize_names: true
|
178
|
+
)
|
179
|
+
).to eq [{ id: 'add-id', name: { add: test_values } }]
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'creates single field from array values on ADD-DISTINCT' do
|
183
|
+
expect(
|
184
|
+
JSON.parse(
|
185
|
+
generator.add(id: 'add-distinct-id') { |doc| doc.add_field(:name, test_values, update: :'add-distinct') },
|
186
|
+
symbolize_names: true
|
187
|
+
)
|
188
|
+
).to eq [{ id: 'add-distinct-id', name: { 'add-distinct': test_values } }]
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'creates single field from array values on REMOVE' do
|
192
|
+
expect(
|
193
|
+
JSON.parse(
|
194
|
+
generator.add(id: 'remove-id') { |doc| doc.add_field(:name, test_values, update: :remove) },
|
195
|
+
symbolize_names: true
|
196
|
+
)
|
197
|
+
).to eq [{ id: 'remove-id', name: { remove: test_values } }]
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'creates single field from array values for child document update' do
|
201
|
+
test_nested_values = [{id: 1, name: 'value1'}, {id: 1, name: 'value2'}]
|
202
|
+
expect(
|
203
|
+
JSON.parse(
|
204
|
+
generator.add(id: 'set-id') { |doc| doc.add_field(:child_documents, test_nested_values, update: :set) },
|
205
|
+
symbolize_names: true
|
206
|
+
)
|
207
|
+
).to eq [{ id: 'set-id', child_documents: { set: test_nested_values } }]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe '#commit' do
|
212
|
+
it 'generates a commit command' do
|
213
|
+
expect(JSON.parse(generator.commit, symbolize_names: true)).to eq(commit: {})
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#optimize' do
|
218
|
+
it 'generates a optimize command' do
|
219
|
+
expect(JSON.parse(generator.optimize, symbolize_names: true)).to eq(optimize: {})
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe '#rollback' do
|
224
|
+
it 'generates a rollback command' do
|
225
|
+
expect(JSON.parse(generator.rollback, symbolize_names: true)).to eq(rollback: {})
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#delete_by_id' do
|
230
|
+
it 'generates a delete_by_id command for single documents' do
|
231
|
+
expect(JSON.parse(generator.delete_by_id('x'), symbolize_names: true)).to eq(delete: 'x')
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'generates a delete_by_id command for an array of documents' do
|
235
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#delete_by_query' do
|
240
|
+
it 'generates a delete_by_id command for single documents' do
|
241
|
+
expect(JSON.parse(generator.delete_by_query('id:x'), symbolize_names: true)).to eq(delete: { query: 'id:x'})
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'generates a delete_by_id command for an array of documents' do
|
245
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Client do
|
4
|
+
context "build_paginated_request" do
|
5
|
+
it "should create the proper solr params and query string" do
|
6
|
+
c = RSolr::Client.new(nil, {})#.extend(RSolr::Pagination::Client)
|
7
|
+
r = c.build_paginated_request 3, 25, "select", {:params => {:q => "test"}}
|
8
|
+
#r[:page].should == 3
|
9
|
+
#r[:per_page].should == 25
|
10
|
+
expect(r[:params]["start"]).to eq(50)
|
11
|
+
expect(r[:params]["rows"]).to eq(25)
|
12
|
+
expect(r[:uri].query).to match(/rows=25/)
|
13
|
+
expect(r[:uri].query).to match(/start=50/)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
context "paginate" do
|
17
|
+
it "should build a paginated request context and call execute" do
|
18
|
+
c = RSolr::Client.new(nil, {})#.extend(RSolr::Pagination::Client)
|
19
|
+
expect(c).to receive(:execute).with(hash_including({
|
20
|
+
#:page => 1,
|
21
|
+
#:per_page => 10,
|
22
|
+
:params => {
|
23
|
+
"rows" => 10,
|
24
|
+
"start" => 0,
|
25
|
+
:wt => :json
|
26
|
+
}
|
27
|
+
}))
|
28
|
+
c.paginate 1, 10, "select"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr do
|
4
|
+
|
5
|
+
it "has a version that can be read via #version or VERSION" do
|
6
|
+
expect(RSolr.version).to eq(RSolr::VERSION)
|
7
|
+
end
|
8
|
+
|
9
|
+
context "connect" do
|
10
|
+
it "should return a RSolr::Client instance" do
|
11
|
+
expect(RSolr.connect).to be_a(RSolr::Client)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context '.solr_escape' do
|
16
|
+
it "adds backslash to Solr query syntax chars" do
|
17
|
+
# per http://lucene.apache.org/core/4_0_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
|
18
|
+
special_chars = [ "+", "-", "&", "|", "!", "(", ")", "{", "}", "[", "]", "^", '"', "~", "*", "?", ":", "\\", "/" ]
|
19
|
+
escaped_str = RSolr.solr_escape("aa#{special_chars.join('aa')}aa")
|
20
|
+
special_chars.each { |c|
|
21
|
+
# note that the ruby code sending the query to Solr will un-escape the backslashes
|
22
|
+
# so the result sent to Solr is ultimately a single backslash in front of the particular character
|
23
|
+
expect(escaped_str).to match "\\#{c}"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
it "leaves other chars alone" do
|
27
|
+
str = "nothing to see here; let's move along people."
|
28
|
+
expect(RSolr.solr_escape(str)).to eq str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Uri do
|
4
|
+
|
5
|
+
let(:uri) { RSolr::Uri }
|
6
|
+
|
7
|
+
context '.params_to_solr' do
|
8
|
+
it "converts Hash to Solr query string w/o a starting ?" do
|
9
|
+
hash = {:q => "gold", :fq => ["mode:one", "level:2"]}
|
10
|
+
query = uri.params_to_solr hash
|
11
|
+
expect(query[0]).not_to eq(??)
|
12
|
+
[/q=gold/, /fq=mode%3Aone/, /fq=level%3A2/].each do |p|
|
13
|
+
expect(query).to match p
|
14
|
+
end
|
15
|
+
expect(query.split('&').size).to eq(3)
|
16
|
+
end
|
17
|
+
it 'should URL escape &' do
|
18
|
+
expect(uri.params_to_solr(:fq => "&")).to eq('fq=%26')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should convert spaces to +' do
|
22
|
+
expect(uri.params_to_solr(:fq => "me and you")).to eq('fq=me+and+you')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should URL escape complex queries, part 1' do
|
26
|
+
my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
|
27
|
+
expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
|
28
|
+
expect(uri.params_to_solr(my_params)).to eq(expected)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should URL escape complex queries, part 2' do
|
32
|
+
my_params = {'q' => '+popularity:[10 TO *] +section:0'}
|
33
|
+
expected = 'q=%2Bpopularity%3A%5B10+TO+*%5D+%2Bsection%3A0'
|
34
|
+
expect(uri.params_to_solr(my_params)).to eq(expected)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|