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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +29 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/CHANGES.txt +63 -260
  6. data/Gemfile +13 -0
  7. data/README.rdoc +177 -63
  8. data/Rakefile +19 -0
  9. data/lib/rsolr/char.rb +6 -0
  10. data/lib/rsolr/client.rb +344 -86
  11. data/lib/rsolr/document.rb +66 -0
  12. data/lib/rsolr/error.rb +182 -0
  13. data/lib/rsolr/field.rb +87 -0
  14. data/lib/rsolr/generator.rb +5 -0
  15. data/lib/rsolr/json.rb +60 -0
  16. data/lib/rsolr/response.rb +95 -0
  17. data/lib/rsolr/uri.rb +25 -0
  18. data/lib/rsolr/version.rb +7 -0
  19. data/lib/rsolr/xml.rb +150 -0
  20. data/lib/rsolr.rb +47 -35
  21. data/rsolr.gemspec +44 -31
  22. data/spec/api/client_spec.rb +423 -0
  23. data/spec/api/document_spec.rb +48 -0
  24. data/spec/api/error_spec.rb +158 -0
  25. data/spec/api/json_spec.rb +248 -0
  26. data/spec/api/pagination_spec.rb +31 -0
  27. data/spec/api/rsolr_spec.rb +31 -0
  28. data/spec/api/uri_spec.rb +37 -0
  29. data/spec/api/xml_spec.rb +255 -0
  30. data/spec/fixtures/basic_configs/_rest_managed.json +1 -0
  31. data/spec/fixtures/basic_configs/currency.xml +67 -0
  32. data/spec/fixtures/basic_configs/lang/stopwords_en.txt +54 -0
  33. data/spec/fixtures/basic_configs/protwords.txt +21 -0
  34. data/spec/fixtures/basic_configs/schema.xml +530 -0
  35. data/spec/fixtures/basic_configs/solrconfig.xml +572 -0
  36. data/spec/fixtures/basic_configs/stopwords.txt +14 -0
  37. data/spec/fixtures/basic_configs/synonyms.txt +29 -0
  38. data/spec/integration/solr5_spec.rb +38 -0
  39. data/spec/lib/rsolr/client_spec.rb +19 -0
  40. data/spec/spec_helper.rb +94 -0
  41. metadata +228 -54
  42. data/lib/rsolr/connection/net_http.rb +0 -48
  43. data/lib/rsolr/connection/requestable.rb +0 -43
  44. data/lib/rsolr/connection/utils.rb +0 -73
  45. data/lib/rsolr/connection.rb +0 -9
  46. data/lib/rsolr/message/document.rb +0 -48
  47. data/lib/rsolr/message/field.rb +0 -20
  48. data/lib/rsolr/message/generator.rb +0 -89
  49. 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