3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use t
::lib
::TestBuilder
;
24 use Test
::More tests
=> 6;
27 use Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
;
29 my $schema = Koha
::Database
->new->schema;
30 $schema->storage->txn_begin;
32 my $se = Test
::MockModule
->new( 'Koha::SearchEngine::Elasticsearch' );
33 $se->mock( 'get_elasticsearch_mappings', sub {
56 sortablenumber__sort
=> {
68 $all_mappings{$self->index} = $mappings;
79 $self->sort_fields($sort_fields->{$self->index});
81 return $all_mappings{$self->index};
84 subtest
'build_authorities_query_compat() tests' => sub {
90 $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new({ 'index' => 'authorities' }),
91 'Creating new query builder object for authorities'
94 my $koha_to_index_name = $Koha::SearchEngine
::Elasticsearch
::QueryBuilder
::koha_to_index_name
;
95 my $search_term = 'a';
96 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
97 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
98 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
99 is
( $query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
},
102 is
( $query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
},
107 $search_term = 'Donald Duck';
108 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
109 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
110 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
111 is
( $query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
},
112 "(Donald*) AND (Duck*)");
114 is
( $query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
},
115 "(Donald*) AND (Duck*)");
119 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
120 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['is'], [$search_term], 'AUTH_TYPE', 'asc' );
121 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
122 is
( $query->{query
}->{bool
}->{must
}[0]->{match_phrase
}->{"_all.phrase"},
125 is
( $query->{query
}->{bool
}->{must
}[0]->{match_phrase
}->{$koha_to_index_name->{$koha_name}.".phrase"},
130 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
131 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'asc' );
132 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
133 is
( $query->{query
}->{bool
}->{must
}[0]->{match_phrase_prefix
}->{"_all.phrase"},
136 is
( $query->{query
}->{bool
}->{must
}[0]->{match_phrase_prefix
}->{$koha_to_index_name->{$koha_name}.".phrase"},
142 my $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingAsc' );
147 'heading__sort' => 'asc'
150 "ascending sort parameter properly formed"
152 $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingDsc' );
157 'heading__sort' => 'desc'
160 "descending sort parameter properly formed"
164 $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
166 $query->{query
}->{bool
}->{filter
},
167 { term
=> { 'authtype' => 'auth_type' } },
168 "authorities type code is used as filter"
173 $qb->build_authorities_query_compat( [ 'tomas' ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
175 'Koha::Exceptions::WrongParameter',
176 'Exception thrown on invalid value in the marclist param';
179 subtest
'build_query tests' => sub {
185 $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new({ 'index' => 'biblios' }),
186 'Creating new query builder object for biblios'
189 my @sort_by = 'title_asc';
190 my @sort_params = $qb->_convert_sort_fields(@sort_by);
192 $options{sort} = \
@sort_params;
193 my $query = $qb->build_query('test', %options);
199 'title__sort.phrase' => {
204 "sort parameter properly formed"
207 t
::lib
::Mocks
::mock_preference
('FacetMaxCount','37');
208 $query = $qb->build_query('test', %options);
209 ok
( defined $query->{aggregations
}{ccode
}{terms
}{size
},'we need to ask for a size or we get only 5 facet' );
210 is
( $query->{aggregations
}{ccode
}{terms
}{size
}, 37,'we ask for the size as defined by the syspref FacetMaxCount');
212 t
::lib
::Mocks
::mock_preference
('DisplayLibraryFacets','both');
213 $query = $qb->build_query();
214 ok
( defined $query->{aggregations
}{homebranch
},
215 'homebranch added to facets if DisplayLibraryFacets=both' );
216 ok
( defined $query->{aggregations
}{holdingbranch
},
217 'holdingbranch added to facets if DisplayLibraryFacets=both' );
218 t
::lib
::Mocks
::mock_preference
('DisplayLibraryFacets','holding');
219 $query = $qb->build_query();
220 ok
( !defined $query->{aggregations
}{homebranch
},
221 'homebranch not added to facets if DisplayLibraryFacets=holding' );
222 ok
( defined $query->{aggregations
}{holdingbranch
},
223 'holdingbranch added to facets if DisplayLibraryFacets=holding' );
224 t
::lib
::Mocks
::mock_preference
('DisplayLibraryFacets','home');
225 $query = $qb->build_query();
226 ok
( defined $query->{aggregations
}{homebranch
},
227 'homebranch added to facets if DisplayLibraryFacets=home' );
228 ok
( !defined $query->{aggregations
}{holdingbranch
},
229 'holdingbranch not added to facets if DisplayLibraryFacets=home' );
231 t
::lib
::Mocks
::mock_preference
( 'QueryAutoTruncate', '' );
233 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
235 $query->{query
}{query_string
}{query
},
237 "query not altered if QueryAutoTruncate disabled"
240 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
242 $query->{query
}{query_string
}{query
},
243 '(title:(donald duck))',
244 'multiple words in a query term are enclosed in parenthesis'
247 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
249 $query->{query
}{query_string
}{query
},
250 '(title:(donald duck)) AND (author:disney)',
251 'multiple query terms are enclosed in parenthesis while a single one is not'
254 my ($simple_query, $query_cgi, $query_desc);
255 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['"donald duck"', 'walt disney'], ['ti', 'au'] );
256 is
($query_cgi, 'idx=ti&q=%22donald%20duck%22&idx=au&q=walt%20disney', 'query cgi ok for multiterm query');
257 is
($query_desc, '(title:("donald duck")) (author:(walt disney))', 'query desc ok for multiterm query');
259 ( undef, $query ) = $qb->build_query_compat( undef, ['2019'], ['yr,st-year'] );
261 $query->{query
}{query_string
}{query
},
262 '(date-of-publication:2019)',
263 'Year in an st-year search is handled properly'
266 ( undef, $query ) = $qb->build_query_compat( undef, ['2018-2019'], ['yr,st-year'] );
268 $query->{query
}{query_string
}{query
},
269 '(date-of-publication:[2018 TO 2019])',
270 'Year range in an st-year search is handled properly'
273 ( undef, $query ) = $qb->build_query_compat( undef, ['-2019'], ['yr,st-year'] );
275 $query->{query
}{query_string
}{query
},
276 '(date-of-publication:[* TO 2019])',
277 'Open start year in year range of an st-year search is handled properly'
280 ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'] );
282 $query->{query
}{query_string
}{query
},
283 '(date-of-publication:[2019 TO *])',
284 'Open end year in year range of an st-year search is handled properly'
287 ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'], ['yr,st-numeric=-2019'] );
289 $query->{query
}{query_string
}{query
},
290 '(date-of-publication:[2019 TO *]) AND copydate:[* TO 2019]',
291 'Open end year in year range of an st-year search is handled properly'
294 # Enable auto-truncation
295 t
::lib
::Mocks
::mock_preference
( 'QueryAutoTruncate', '1' );
297 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
299 $query->{query
}{query_string
}{query
},
301 "simple query is auto truncated when QueryAutoTruncate enabled"
304 # Ensure reserved words are not truncated
305 ( undef, $query ) = $qb->build_query_compat( undef,
306 ['donald or duck and mickey not mouse'] );
308 $query->{query
}{query_string
}{query
},
309 "(donald* or duck* and mickey* not mouse*)",
310 "reserved words are not affected by QueryAutoTruncate"
313 ( undef, $query ) = $qb->build_query_compat( undef, ['donald* duck*'] );
315 $query->{query
}{query_string
}{query
},
317 "query with '*' is unaltered when QueryAutoTruncate is enabled"
320 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck and the mouse'] );
322 $query->{query
}{query_string
}{query
},
323 "(donald* duck* and the* mouse*)",
324 "individual words are all truncated and stopwords ignored"
327 ( undef, $query ) = $qb->build_query_compat( undef, ['*'] );
329 $query->{query
}{query_string
}{query
},
331 "query of just '*' is unaltered when QueryAutoTruncate is enabled"
334 ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck"'] );
336 $query->{query
}{query_string
}{query
},
338 "query with quotes is unaltered when QueryAutoTruncate is enabled"
342 ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck" and "the mouse"'] );
344 $query->{query
}{query_string
}{query
},
345 '("donald duck" and "the mouse")',
346 "all quoted strings are unaltered if more than one in query"
349 ( undef, $query ) = $qb->build_query_compat( undef, ['barcode:123456'] );
351 $query->{query
}{query_string
}{query
},
353 "query of specific field is truncated"
356 ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number:"123456"'] );
358 $query->{query
}{query_string
}{query
},
359 '(local-number:"123456")',
360 "query of specific field including hyphen and quoted is not truncated, field name is converted to lower case"
363 ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number:123456'] );
365 $query->{query
}{query_string
}{query
},
366 '(local-number:123456*)',
367 "query of specific field including hyphen and not quoted is truncated, field name is converted to lower case"
370 ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number.raw:123456'] );
372 $query->{query
}{query_string
}{query
},
373 '(local-number.raw:123456*)',
374 "query of specific field including period and not quoted is truncated, field name is converted to lower case"
377 ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number.raw:"123456"'] );
379 $query->{query
}{query_string
}{query
},
380 '(local-number.raw:"123456")',
381 "query of specific field including period and quoted is not truncated, field name is converted to lower case"
384 ( undef, $query ) = $qb->build_query_compat( undef, ['J.R.R'] );
386 $query->{query
}{query_string
}{query
},
388 "query including period is truncated but not split at periods"
391 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'] );
393 $query->{query
}{query_string
}{query
},
394 '(title:"donald duck")',
395 "query of specific field is not truncated when surrounded by quotes"
398 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
400 $query->{query
}{query_string
}{query
},
401 '(title:(donald* duck*))',
402 'words of a multi-word term are properly truncated'
405 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
407 $query->{query
}{query_string
}{query
},
408 '(title:(donald* duck*)) AND (author:disney*)',
409 'words of a multi-word term and single-word term are properly truncated'
412 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress
=> 1 } );
414 $query->{query
}{query_string
}{query
},
415 '(title:"donald duck") AND suppress:0',
416 "query of specific field is added AND suppress:0"
419 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress
=> 0 } );
421 $query->{query
}{query_string
}{query
},
422 '(title:"donald duck")',
423 "query of specific field is not added AND suppress:0"
425 is
($query_cgi, 'idx=&q=title%3A%22donald%20duck%22', 'query cgi');
426 is
($query_desc, 'title:"donald duck"', 'query desc ok');
430 subtest
'build query from form subtests' => sub {
433 my $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new({ 'index' => 'authorities' }),
434 #when searching for authorities from a record the form returns marclist with blanks for unentered terms
435 my @marclist = ('mainmainentry','mainentry','match', 'all');
436 my @values = ( undef, 'Hamilton', undef, undef);
437 my @operator = ( 'contains', 'contains', 'contains', 'contains');
439 my $query = $qb->build_authorities_query_compat( \
@marclist, undef,
440 undef, \
@operator , \
@values, 'AUTH_TYPE', 'asc' );
441 is
($query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
}, "Hamilton*","Expected search is populated");
442 is
( scalar @
{ $query->{query
}->{bool
}->{must
} }, 1,"Only defined search is populated");
444 @values[2] = 'Jefferson';
445 $query = $qb->build_authorities_query_compat( \
@marclist, undef,
446 undef, \
@operator , \
@values, 'AUTH_TYPE', 'asc' );
447 is
($query->{query
}->{bool
}->{must
}[0]->{query_string
}->{query
}, "Hamilton*","First index searched as expected");
448 is
($query->{query
}->{bool
}->{must
}[1]->{query_string
}->{query
}, "Jefferson*","Second index searched when populated");
449 is
( scalar @
{ $query->{query
}->{bool
}->{must
} }, 2,"Only defined searches are populated");
454 subtest
'build_query with weighted fields tests' => sub {
457 my $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new( { index => 'mydb' } );
458 my $db_builder = t
::lib
::TestBuilder
->new();
460 Koha
::SearchFields
->search({})->delete;
463 source
=> 'SearchField',
472 source
=> 'SearchField',
481 source
=> 'SearchField',
489 my ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
490 undef, undef, undef, { weighted_fields
=> 1 });
492 my $fields = $query->{query
}{query_string
}{fields
};
493 is
(scalar(@
$fields), 3, 'Search is done on 3 fields');
494 is
($fields->[0], '_all', 'First search field is _all');
495 is
($fields->[1], 'title^25.00', 'Second search field is title');
496 is
($fields->[2], 'subject^15.00', 'Third search field is subject');
499 subtest
"_convert_sort_fields() tests" => sub {
505 $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new({ 'index' => 'biblios' }),
506 'Creating new query builder object for biblios'
509 my @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_dsc ));
513 { field
=> 'local-classification', direction
=> 'asc' },
514 { field
=> 'author', direction
=> 'desc' }
516 'sort fields should have been split correctly'
519 # We could expect this to pass, but direction is undef instead of 'desc'
520 @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_desc ));
524 { field
=> 'local-classification', direction
=> 'asc' },
525 { field
=> 'author', direction
=> 'desc' }
527 'sort fields should have been split correctly'
531 subtest
"_sort_field() tests" => sub {
537 $qb = Koha
::SearchEngine
::Elasticsearch
::QueryBuilder
->new({ 'index' => 'biblios' }),
538 'Creating new query builder object for biblios'
541 my $f = $qb->_sort_field('title');
544 'title__sort.phrase',
545 'title sort mapped correctly'
548 $f = $qb->_sort_field('subject');
552 'subject sort mapped correctly'
555 $f = $qb->_sort_field('itemnumber');
559 'itemnumber sort mapped correctly'
562 $f = $qb->_sort_field('sortablenumber');
565 'sortablenumber__sort',
566 'sortablenumber sort mapped correctly'
570 $schema->storage->txn_rollback;