diff --git a/spec/lib/search_index_backend_spec.rb b/spec/lib/search_index_backend_spec.rb new file mode 100644 index 000000000..708f9a955 --- /dev/null +++ b/spec/lib/search_index_backend_spec.rb @@ -0,0 +1,101 @@ +require 'rails_helper' + +RSpec.describe SearchIndexBackend do + describe '.build_query' do + subject(:query) { SearchIndexBackend.build_query('', query_extension: params) } + let(:params) { { 'bool' => { 'filter' => { 'term' => { 'a' => 'b' } } } } } + + it 'coerces :query_extension hash keys to symbols' do + expect(query.dig(:query, :bool, :filter, :term, :a)).to eq('b') + end + end + + describe '.search' do + subject(:search) { SearchIndexBackend.search(query, index, limit: 3000) } + + context 'for query with no results' do + let(:query) { 'preferences.notification_sound.enabled:*' } + + context 'on a single index' do + let(:index) { 'User' } + + it { is_expected.to be_nil } + end + + context 'on multiple indices' do + let(:index) { %w[User Organization] } + + it { is_expected.to be_an(Array).and not_include(nil).and be_empty } + end + end + end + + describe '.append_wildcard_to_simple_query' do + context 'with "simple" queries' do + let(:queries) { <<~QUERIES.lines.map { |x| x.split('#')[0] }.map(&:strip) } + M + Max + Max. # dot and underscore are acceptable characters in simple queries + A_ + A_B + äöü + 123 + *ax # wildcards are allowed in simple queries + Max* + M*x + M?x + test@example.com + test@example. + test@example + test@ + QUERIES + + it 'appends a * to the original query' do + expect(queries.map(&SearchIndexBackend.method(:append_wildcard_to_simple_query))) + .to eq(queries.map { |q| "#{q}*" }) + end + end + + context 'with "complex" queries (using search operators)' do + let(:queries) { <<~QUERIES.lines.map { |x| x.split('#')[0] }.map(&:strip) } + title:"some words with spaces" # exact phrase / without quotation marks " an AND search for the words will be performed (in Zammad 1.5 and lower an OR search will be performed) + title:"some wor*" # exact phrase beginning with "some wor*" will be searched + created_at:[2017-01-01 TO 2017-12-31] # a time range + created_at:>now-1h # created within last hour + state:new OR state:open + (state:new OR state:open) OR priority:"3 normal" + (state:new OR state:open) AND customer.lastname:smith + state:(new OR open) AND title:(full text search) # state: new OR open & title: full OR text OR search + tags: "some tag" + owner.email: "bod@example.com" AND state: (new OR open OR pending*) # show all open tickets of a certain agent + state:closed AND _missing_:tag # all closed objects without tags + article_count: [1 TO 5] # tickets with 1 to 5 articles + article_count: [10 TO *] # tickets with 10 or more articles + article.from: bob # also article.from can be used + article.body: heat~ # using the fuzzy operator will also find terms that are similar, in this case also "head" + article.body: /joh?n(ath[oa]n)/ # using regular expressions + user:M + user:Max + user:Max. + user:Max* + organization:A_B + organization:A_B* + user: M + user: Max + user: Max. + user: Max* + organization: A_B + organization: A_B* + id:123 + number:123 + id:"123" + number:"123" + QUERIES + + it 'returns the original query verbatim' do + expect(queries.map(&SearchIndexBackend.method(:append_wildcard_to_simple_query))) + .to eq(queries) + end + end + end +end diff --git a/spec/support/negated_matchers.rb b/spec/support/negated_matchers.rb index 93f5b66e5..539f78048 100644 --- a/spec/support/negated_matchers.rb +++ b/spec/support/negated_matchers.rb @@ -1 +1,2 @@ RSpec::Matchers.define_negated_matcher :not_change, :change +RSpec::Matchers.define_negated_matcher :not_include, :include diff --git a/test/unit/search_index_backend_test.rb b/test/unit/search_index_backend_test.rb deleted file mode 100644 index dbe2a230f..000000000 --- a/test/unit/search_index_backend_test.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'test_helper' - -class SearchIndexBackendTest < ActiveSupport::TestCase - test 'query extension keys are normalized to symbols' do - query_strings = SearchIndexBackend.build_query('', query_extension: { 'bool' => { 'filter' => { 'term' => { 'a' => 'b' } } } }) - query_symbols = SearchIndexBackend.build_query('', query_extension: { bool: { filter: { term: { a: 'b' } } } }) - - assert_equal query_strings, query_symbols - assert_not_nil query_strings.dig(:query, :bool, :filter, :term, :a) - end - - test 'search with ES off never returns nil in array' do - index_one = SearchIndexBackend.search('preferences.notification_sound.enabled:*', 'User', limit: 3000) - index_multi = SearchIndexBackend.search('preferences.notification_sound.enabled:*', %w[User Organization], limit: 3000) - - assert_nil index_one - assert index_multi.empty? - end - - test 'simple_query_append_wildcard correctly modifies simple queries' do - def clean_queries(query_string) - query_string.each_line - .map(&:strip) - .reject(&:empty?) - .map { |x| x.split('#')[0] } - end - - # Examples of complex queries from https://docs.zammad.org/en/latest/general-search.html - complex_queries = clean_queries %( - title:”some words with spaces” # exact phrase / without quotation marks ” an AND search for the words will be performed (in Zammad 1.5 and lower an OR search will be performed) - title:”some wor*” # exact phrase beginning with “some wor*” will be searched - created_at:[2017-01-01 TO 2017-12-31] # a time range - created_at:>now-1h # created within last hour - state:new OR state:open - (state:new OR state:open) OR priority:”3 normal” - (state:new OR state:open) AND customer.lastname:smith - state:(new OR open) AND title:(full text search) # state: new OR open & title: full OR text OR search - tags: “some tag” - owner.email: “bod@example.com” AND state: (new OR open OR pending*) # show all open tickets of a certain agent - state:closed AND _missing_:tag # all closed objects without tags - article_count: [1 TO 5] # tickets with 1 to 5 articles - article_count: [10 TO *] # tickets with 10 or more articles - article.from: bob # also article.from can be used - article.body: heat~ # using the fuzzy operator will also find terms that are similar, in this case also “head” - article.body: /joh?n(ath[oa]n)/ # using regular expressions - user:M - user:Max - user:Max. - user:Max* - organization:A_B - organization:A_B* - user: M - user: Max - user: Max. - user: Max* - organization: A_B - organization: A_B* - id:123 - number:123 - id:"123" - number:"123" - ) - - simple_queries = clean_queries %( - M - - Max - Max. # dot and underscore are acceptable characters in simple queries - A_ - A_B - äöü - 123 - *ax # wildcards are allowed in simple queries - Max* - M*x - M?x - test@example.com - test@example. - test@example - test@ - ) - - complex_queries.each do |query| - result_query = SearchIndexBackend.append_wildcard_to_simple_query(query) - # Verify that the result query is still the same as the input query - assert_equal(query, result_query) - end - - simple_queries.each do |query| - result_query = SearchIndexBackend.append_wildcard_to_simple_query(query) - # Verify that * is correctly appended to simple queries - assert_equal(query + '*', result_query) - end - end -end