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>.
24 use t::lib::TestBuilder;
25 use Test::More tests => 8;
27 use List::Util qw( all );
30 use Koha::SearchEngine::Elasticsearch::QueryBuilder;
31 use Koha::SearchFilters;
33 my $schema = Koha::Database->new->schema;
34 $schema->storage->txn_begin;
36 my $se = Test::MockModule->new( 'Koha::SearchEngine::Elasticsearch' );
37 $se->mock( 'get_elasticsearch_mappings', sub {
54 'subject-heading-thesaurus' => {
58 'subject-heading-thesaurus-conventions' => {
67 sortablenumber__sort => {
85 'match-heading-see-from' => {
90 $all_mappings{$self->index} = $mappings;
101 $self->sort_fields($sort_fields->{$self->index});
103 return $all_mappings{$self->index};
106 subtest 'build_authorities_query_compat() tests' => sub {
113 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'authorities' }),
114 'Creating new query builder object for authorities'
117 my $koha_to_index_name = $Koha::SearchEngine::Elasticsearch::QueryBuilder::koha_to_index_name;
118 my $search_term = 'a';
119 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
120 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
121 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
122 is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
125 is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
128 is( $query->{query}->{bool}->{must}[0]->{query_string}->{analyze_wildcard}, JSON::true, 'Set analyze_wildcard true' );
129 is( $query->{query}->{bool}->{must}[0]->{query_string}->{lenient}, JSON::true, 'Set lenient true' );
132 $search_term = 'Donald Duck';
133 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
134 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
135 is( $query->{query}->{bool}->{must}[0]->{query_string}->{query}, "(Donald*) AND (Duck*)" );
136 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
137 isa_ok( $query->{query}->{bool}->{must}[0]->{query_string}->{fields}, 'ARRAY')
139 is( $query->{query}->{bool}->{must}[0]->{query_string}->{default_field}, $koha_to_index_name->{$koha_name} );
143 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
144 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['is'], [$search_term], 'AUTH_TYPE', 'asc' );
145 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
147 $query->{query}->{bool}->{must}[0]->{multi_match}->{query},
150 my $all_matches = all { /\.ci_raw$/ }
151 @{$query->{query}->{bool}->{must}[0]->{multi_match}->{fields}};
152 ok( $all_matches, 'Correct fields parameter for "is" query in "any" or "all"' );
155 $query->{query}->{bool}->{must}[0]->{term}->{$koha_to_index_name->{$koha_name} . ".ci_raw"},
161 foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
162 my $query = $qb->build_authorities_query_compat( [ $koha_name ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'asc' );
163 if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
164 my $all_matches = all { (%{$_->{prefix}})[0] =~ /\.ci_raw$/ && (%{$_->{prefix}})[1] eq "Donald Duck" }
165 @{$query->{query}->{bool}->{must}[0]->{bool}->{should}};
166 ok( $all_matches, "Correct multiple prefix query" );
168 is( $query->{query}->{bool}->{must}[0]->{prefix}->{$koha_to_index_name->{$koha_name} . ".ci_raw"}, "Donald Duck" );
173 my $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingAsc' );
178 'heading__sort' => 'asc'
181 "ascending sort parameter properly formed"
183 $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingDsc' );
188 'heading__sort' => 'desc'
191 "descending sort parameter properly formed"
195 $query = $qb->build_authorities_query_compat( [ 'mainentry' ], undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
197 $query->{query}->{bool}->{filter},
198 { term => { 'authtype.raw' => 'AUTH_TYPE' } },
199 "authorities type code is used as filter"
202 # Authorities marclist check
204 $query = $qb->build_authorities_query_compat( [ 'tomas','mainentry' ], undef, undef, ['contains'], [$search_term,$search_term], 'AUTH_TYPE', 'asc' )
206 qr/Unknown search field tomas/,
207 "Warning for unknown field in marclist";
209 $query->{query}->{bool}->{must}[0]->{query_string}->{default_field},
211 "If no mapping for marclist the index is passed through as defined"
214 $query->{query}->{bool}->{must}[1]->{query_string}{default_field},
216 "If mapping found for marclist the index is passed through converted"
221 subtest 'build_query tests' => sub {
227 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
228 'Creating new query builder object for biblios'
231 my @sort_by = 'title_asc';
232 my @sort_params = $qb->_convert_sort_fields(@sort_by);
234 $options{sort} = \@sort_params;
235 my $query = $qb->build_query('test', %options);
246 "sort parameter properly formed"
249 t::lib::Mocks::mock_preference('FacetMaxCount','37');
250 t::lib::Mocks::mock_preference('DisplayLibraryFacets','both');
251 $query = $qb->build_query('test', %options);
252 ok( defined $query->{aggregations}{ccode}{terms}{size},'we need to ask for a size or we get only 5 facet' );
253 is( $query->{aggregations}{ccode}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount');
254 is( $query->{aggregations}{homebranch}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount for homebranch');
255 is( $query->{aggregations}{holdingbranch}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount for holdingbranch');
257 t::lib::Mocks::mock_preference('DisplayLibraryFacets','both');
258 $query = $qb->build_query();
259 ok( defined $query->{aggregations}{homebranch},
260 'homebranch added to facets if DisplayLibraryFacets=both' );
261 ok( defined $query->{aggregations}{holdingbranch},
262 'holdingbranch added to facets if DisplayLibraryFacets=both' );
263 t::lib::Mocks::mock_preference('DisplayLibraryFacets','holding');
264 $query = $qb->build_query();
265 ok( !defined $query->{aggregations}{homebranch},
266 'homebranch not added to facets if DisplayLibraryFacets=holding' );
267 ok( defined $query->{aggregations}{holdingbranch},
268 'holdingbranch added to facets if DisplayLibraryFacets=holding' );
269 t::lib::Mocks::mock_preference('DisplayLibraryFacets','home');
270 $query = $qb->build_query();
271 ok( defined $query->{aggregations}{homebranch},
272 'homebranch added to facets if DisplayLibraryFacets=home' );
273 ok( !defined $query->{aggregations}{holdingbranch},
274 'holdingbranch not added to facets if DisplayLibraryFacets=home' );
276 t::lib::Mocks::mock_preference( 'QueryAutoTruncate', '' );
278 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
280 $query->{query}{query_string}{query},
282 "query not altered if QueryAutoTruncate disabled"
285 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['kw,phr'] );
287 $query->{query}{query_string}{query},
289 "keyword as phrase correctly quotes search term and strips index"
292 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
294 $query->{query}{query_string}{query},
295 '(title:(donald duck))',
296 'multiple words in a query term are enclosed in parenthesis'
299 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
301 $query->{query}{query_string}{query},
302 '(title:(donald duck)) AND (author:disney)',
303 'multiple query terms are enclosed in parenthesis while a single one is not'
306 my ($simple_query, $query_cgi, $query_desc);
307 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['"donald duck"', 'walt disney'], ['ti', 'au'] );
308 is($query_cgi, 'idx=ti&q=%22donald%20duck%22&idx=au&q=walt%20disney', 'query cgi ok for multiterm query');
309 is($query_desc, '(title:("donald duck")) (author:(walt disney))', 'query desc ok for multiterm query');
311 ( undef, $query ) = $qb->build_query_compat( undef, ['2019'], ['yr,st-year'] );
313 $query->{query}{query_string}{query},
314 '(date-of-publication:2019)',
315 'Year in an st-year search is handled properly'
318 ( undef, $query ) = $qb->build_query_compat( undef, ['2018-2019'], ['yr,st-year'] );
320 $query->{query}{query_string}{query},
321 '(date-of-publication:[2018 TO 2019])',
322 'Year range in an st-year search is handled properly'
325 ( undef, $query ) = $qb->build_query_compat( undef, ['-2019'], ['yr,st-year'] );
327 $query->{query}{query_string}{query},
328 '(date-of-publication:[* TO 2019])',
329 'Open start year in year range of an st-year search is handled properly'
332 ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'] );
334 $query->{query}{query_string}{query},
335 '(date-of-publication:[2019 TO *])',
336 'Open end year in year range of an st-year search is handled properly'
339 ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'], ['yr,st-numeric=-2019'] );
341 $query->{query}{query_string}{query},
342 '(date-of-publication:[2019 TO *]) AND date-of-publication:[* TO 2019]',
343 'Open end year in year range of an st-year search is handled properly'
346 ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'],
347 ['yr,st-numeric:-2019','yr,st-numeric:2005','yr,st-numeric:1984-2022'] );
349 $query->{query}{query_string}{query},
350 '(date-of-publication:[2019 TO *]) AND (date-of-publication:[* TO 2019]) AND (date-of-publication:2005) AND (date-of-publication:[1984 TO 2022])',
351 'Limit on year search is handled properly when colon used'
354 # Enable auto-truncation
355 t::lib::Mocks::mock_preference( 'QueryAutoTruncate', '1' );
357 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
359 $query->{query}{query_string}{query},
361 "simple query is auto truncated when QueryAutoTruncate enabled"
364 # Ensure reserved words are not truncated
365 ( undef, $query ) = $qb->build_query_compat( undef,
366 ['donald or duck and mickey not mouse'] );
368 $query->{query}{query_string}{query},
369 "(donald* or duck* and mickey* not mouse*)",
370 "reserved words are not affected by QueryAutoTruncate"
373 ( undef, $query ) = $qb->build_query_compat( undef, ['donald* duck*'] );
375 $query->{query}{query_string}{query},
377 "query with '*' is unaltered when QueryAutoTruncate is enabled"
380 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck and the mouse'] );
382 $query->{query}{query_string}{query},
383 "(donald* duck* and the* mouse*)",
384 "individual words are all truncated and stopwords ignored"
387 ( undef, $query ) = $qb->build_query_compat( undef, ['*'] );
389 $query->{query}{query_string}{query},
391 "query of just '*' is unaltered when QueryAutoTruncate is enabled"
394 ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck"'], undef, ['available'] );
396 $query->{query}{query_string}{query},
397 '("donald duck") AND available:true',
398 "query with quotes is unaltered when QueryAutoTruncate is enabled"
402 ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck" and "the mouse"'] );
404 $query->{query}{query_string}{query},
405 '("donald duck" and "the mouse")',
406 "all quoted strings are unaltered if more than one in query"
409 # Reset ESPreventAutoTruncate syspref
410 t::lib::Mocks::mock_preference( 'ESPreventAutoTruncate', '' );
412 ( undef, $query ) = $qb->build_query_compat( undef, ['barcode:123456'] );
414 $query->{query}{query_string}{query},
416 "query of specific field is truncated"
419 ( undef, $query ) = $qb->build_query_compat( undef, ['Personal-name:"donald"'] );
421 $query->{query}{query_string}{query},
422 '(personal-name:"donald")',
423 "query of specific field including hyphen and quoted is not truncated, field name is converted to lower case"
426 ( undef, $query ) = $qb->build_query_compat( undef, ['Personal-name:donald'] );
428 $query->{query}{query_string}{query},
429 '(personal-name:donald*)',
430 "query of specific field including hyphen and not quoted is truncated, field name is converted to lower case"
433 ( undef, $query ) = $qb->build_query_compat( undef, ['Personal-name.raw:donald'] );
435 $query->{query}{query_string}{query},
436 '(personal-name.raw:donald*)',
437 "query of specific field including period and not quoted is truncated, field name is converted to lower case"
440 ( undef, $query ) = $qb->build_query_compat( undef, ['Personal-name.raw:"donald"'] );
442 $query->{query}{query_string}{query},
443 '(personal-name.raw:"donald")',
444 "query of specific field including period and quoted is not truncated, field name is converted to lower case"
447 # Set ESPreventAutoTruncate syspref
448 t::lib::Mocks::mock_preference( 'ESPreventAutoTruncate', 'barcode' );
450 ( undef, $query ) = $qb->build_query_compat( undef, ['barcode:123456'] );
452 $query->{query}{query_string}{query},
454 "query of specific field excluded by ESPreventAutoTruncate is not truncated"
457 ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number:123456'] );
459 $query->{query}{query_string}{query},
460 '(local-number:123456)',
461 "query of identifier is not truncated even if QueryAutoTruncate is set"
464 ( undef, $query ) = $qb->build_query_compat( undef, ['onloan:true'] );
466 $query->{query}{query_string}{query},
468 "query of boolean type field is not truncated even if QueryAutoTruncate is set"
471 ( undef, $query ) = $qb->build_query_compat( undef, ['J.R.R'] );
473 $query->{query}{query_string}{query},
475 "query including period is truncated but not split at periods"
478 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'] );
480 $query->{query}{query_string}{query},
481 '(title:"donald duck")',
482 "query of specific field is not truncated when surrounded by quotes"
485 ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
487 $query->{query}{query_string}{query},
488 '(title:(donald* duck*))',
489 'words of a multi-word term are properly truncated'
492 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
494 $query->{query}{query_string}{query},
495 '(title:(donald* duck*)) AND (author:disney*)',
496 'words of a multi-word term and single-word term are properly truncated'
499 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress => 1 } );
501 $query->{query}{query_string}{query},
502 '(title:"donald duck") AND suppress:false',
503 "query of specific field is added AND suppress:false"
506 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress => 0 } );
508 $query->{query}{query_string}{query},
509 '(title:"donald duck")',
510 "query of specific field is not added AND suppress:0"
513 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan'] );
515 $query->{query}{query_string}{query},
516 '(title:"donald duck") AND author:("Dillinger Escaplan")',
517 "Simple query with limit term quoted in parentheses"
520 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan', 'itype:BOOK'] );
522 $query->{query}{query_string}{query},
523 '(title:"donald duck") AND (author:("Dillinger Escaplan")) AND (itype:("BOOK"))',
524 "Simple query with each limit's term quoted in parentheses"
526 is($query_cgi, 'idx=&q=title%3A%22donald%20duck%22', 'query cgi');
527 is($query_desc, 'title:"donald duck"', 'query desc ok');
529 ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan', 'mc-itype,phr:BOOK', 'mc-itype,phr:CD'] );
531 $query->{query}{query_string}{query},
532 '(title:"donald duck") AND (author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))',
533 "Limits quoted correctly when passed as phrase"
536 ( undef, $query ) = $qb->build_query_compat( ['OR'], ['title:"donald duck"', 'author:"Dillinger Escaplan"'], undef, ['itype:BOOK'] );
538 $query->{query}{query_string}{query},
539 '((title:"donald duck") OR (author:"Dillinger Escaplan")) AND itype:("BOOK")',
540 "OR query with limit"
543 ( undef, $query ) = $qb->build_query_compat( undef, undef, undef, ['itype:BOOK'] );
545 $query->{query}{query_string}{query},
551 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['new'], ['au'], undef, undef, 1 );
553 $query->{query}{query_string}{query},
555 "scan query is properly formed"
558 $query->{aggregations}{'author'}{'terms'},
560 field => 'author__facet',
561 order => { '_key' => 'asc' },
562 include => '[nN][eE][wW].*'
564 "scan aggregation request is properly formed"
566 is($query_cgi, 'idx=au&q=new&scan=1', 'query cgi');
567 is($query_desc, 'new', 'query desc ok');
569 ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['new'], [], undef, undef, 1 );
571 $query->{query}{query_string}{query},
573 "scan query is properly formed"
576 $query->{aggregations}{'subject'}{'terms'},
578 field => 'subject__facet',
579 order => { '_key' => 'asc' },
580 include => '[nN][eE][wW].*'
582 "scan aggregation request is properly formed"
584 is($query_cgi, 'idx=&q=new&scan=1', 'query cgi');
585 is($query_desc, 'new', 'query desc ok');
587 my( $limit, $limit_cgi, $limit_desc );
588 ( undef, $query, $simple_query, $query_cgi, $query_desc, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( ['AND'], ['kw:""'], undef, ['author:Dillinger Escaplan', 'mc-itype,phr:BOOK', 'mc-itype,phr:CD'] );
589 is( $limit, '(author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))', "Limit formed correctly when no search terms");
590 is( $limit_cgi,'&limit=author%3ADillinger%20Escaplan&limit=mc-itype%2Cphr%3ABOOK&limit=mc-itype%2Cphr%3ACD', "Limit CGI formed correctly when no search terms");
591 is( $limit_desc,'(author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))',"Limit desc formed correctly when no search terms");
595 subtest 'build query from form subtests' => sub {
598 my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'authorities' }),
599 #when searching for authorities from a record the form returns marclist with blanks for unentered terms
600 my @marclist = ('mainmainentry','mainentry','match', 'all');
601 my @values = ( undef, 'Hamilton', undef, undef);
602 my @operator = ( 'contains', 'contains', 'contains', 'contains');
604 my $query = $qb->build_authorities_query_compat( \@marclist, undef,
605 undef, \@operator , \@values, 'AUTH_TYPE', 'asc' );
606 is($query->{query}->{bool}->{must}[0]->{query_string}->{query}, "Hamilton*","Expected search is populated");
607 is( scalar @{ $query->{query}->{bool}->{must} }, 1,"Only defined search is populated");
609 @values[2] = 'Jefferson';
610 $query = $qb->build_authorities_query_compat( \@marclist, undef,
611 undef, \@operator , \@values, 'AUTH_TYPE', 'asc' );
612 is($query->{query}->{bool}->{must}[0]->{query_string}->{query}, "Hamilton*","First index searched as expected");
613 is($query->{query}->{bool}->{must}[1]->{query_string}->{query}, "Jefferson*","Second index searched when populated");
614 is( scalar @{ $query->{query}->{bool}->{must} }, 2,"Only defined searches are populated");
619 subtest 'build_query with weighted fields tests' => sub {
622 $se->mock( '_load_elasticsearch_mappings', sub {
632 marc_type => 'marc21',
636 label => 'headingmain',
642 marc_type => 'marc21',
654 marc_type => 'marc21',
663 marc_field => '952d',
664 marc_type => 'marc21',
667 marc_field => '9955',
668 marc_type => 'marc21',
679 marc_type => 'marc21'
688 marc_field => '600a',
689 marc_type => 'marc21'
696 my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'biblios' } );
697 Koha::SearchFields->search({})->delete;
698 Koha::SearchEngine::Elasticsearch->reset_elasticsearch_mappings();
701 $search_field = Koha::SearchFields->find({ name => 'title' });
702 $search_field->update({ weight => 25.0 });
703 $search_field = Koha::SearchFields->find({ name => 'subject' });
704 $search_field->update({ weight => 15.5 });
705 Koha::SearchEngine::Elasticsearch->clear_search_fields_cache();
707 my ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
708 undef, undef, undef, { weighted_fields => 1 });
710 my $fields = $query->{query}{query_string}{fields};
712 is(@{$fields}, 2, 'Search field with no searchable mappings has been excluded');
714 my @found = grep { $_ eq 'title^25.00' } @{$fields};
715 is(@found, 1, 'Search field title has correct weight');
717 @found = grep { $_ eq 'subject^15.50' } @{$fields};
718 is(@found, 1, 'Search field subject has correct weight');
720 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
721 undef, undef, undef, { weighted_fields => 1, is_opac => 1 });
723 $fields = $query->{query}{query_string}{fields};
728 'Only OPAC search fields are used when opac search is performed'
731 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'authorities' } );
732 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
733 undef, undef, undef, { weighted_fields => 1 });
734 $fields = $query->{query}{query_string}{fields};
735 is_deeply( [sort @$fields], ['heading','headingmain'],'Authorities fields retrieve for authorities index');
737 ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
738 undef, undef, undef, { weighted_fields => 1, is_opac => 1 });
739 $fields = $query->{query}{query_string}{fields};
740 is_deeply($fields,['headingmain'],'Only opac authorities fields retrieved for authorities index is is_opac');
744 subtest 'build_query_compat() SearchLimitLibrary tests' => sub {
748 $schema->storage->txn_begin;
750 my $builder = t::lib::TestBuilder->new;
752 my $branch_1 = $builder->build_object({ class => 'Koha::Libraries' });
753 my $branch_2 = $builder->build_object({ class => 'Koha::Libraries' });
754 my $group = $builder->build_object({ class => 'Koha::Library::Groups', value => {
755 ft_search_groups_opac => 1,
756 ft_search_groups_staff => 1,
761 my $group_1 = $builder->build_object({ class => 'Koha::Library::Groups', value => {
762 parent_id => $group->id,
763 branchcode => $branch_1->id
766 my $group_2 = $builder->build_object({ class => 'Koha::Library::Groups', value => {
767 parent_id => $group->id,
768 branchcode => $branch_2->id
771 my $groupid = $group->id;
772 my @branchcodes = sort { $a cmp $b } ( $branch_1->id, $branch_2->id );
775 my $query_builder = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
776 t::lib::Mocks::mock_preference('SearchLimitLibrary', 'both');
777 my ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
778 $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
779 is( $limit, '(homebranch: "CPL" OR holdingbranch: "CPL")', "Branch limit expanded to home/holding branch");
780 is( $limit_desc, '(homebranch: "CPL" OR holdingbranch: "CPL")', "Limit description correctly expanded");
781 is( $limit_cgi, '&limit=branch%3ACPL', "Limit cgi does not get expanded");
782 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
783 $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
784 is( $limit, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\" OR holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Multibranch limit expanded to home/holding branches");
785 is( $limit_desc, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\" OR holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Multibranch limit description correctly expanded");
786 is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Multibranch limit cgi does not get expanded");
788 t::lib::Mocks::mock_preference('SearchLimitLibrary', 'homebranch');
789 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
790 $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
791 is( $limit, "(homebranch: \"CPL\")", "branch limit expanded to home branch");
792 is( $limit_desc, "(homebranch: \"CPL\")", "limit description correctly expanded");
793 is( $limit_cgi, "&limit=branch%3ACPL", "limit cgi does not get expanded");
794 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
795 $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
796 is( $limit, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\")", "branch limit expanded to home branch");
797 is( $limit_desc, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\")", "limit description correctly expanded");
798 is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Limit cgi does not get expanded");
800 t::lib::Mocks::mock_preference('SearchLimitLibrary', 'holdingbranch');
801 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
802 $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
803 is( $limit, "(holdingbranch: \"CPL\")", "branch limit expanded to holding branch");
804 is( $limit_desc, "(holdingbranch: \"CPL\")", "Limit description correctly expanded");
805 is( $limit_cgi, "&limit=branch%3ACPL", "Limit cgi does not get expanded");
806 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
807 $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
808 is( $limit, "(holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "branch limit expanded to holding branch");
809 is( $limit_desc, "(holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Limit description correctly expanded");
810 is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Limit cgi does not get expanded");
814 subtest "Handle search filters" => sub {
820 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
821 'Creating new query builder object for biblios'
824 my $filter = Koha::SearchFilter->new({
826 query => q|{"operands":["cat","bat","rat"],"indexes":["kw","ti","au"],"operators":["AND","OR"]}|,
827 limits => q|{"limits":["mc-itype,phr:BK","mc-itype,phr:MU","available"]}|,
829 my $filter_id = $filter->id;
831 my ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] );
833 is( $limit,q{(available:true) AND ((cat) AND title:(bat) OR author:(rat)) AND itype:(("BK") OR ("MU"))},"Limit correctly formed");
834 is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded");
835 is( $limit_desc,q{(available:true) AND ((cat) AND title:(bat) OR author:(rat)) AND itype:(("BK") OR ("MU"))},"Limit description is correctly expanded");
837 $filter = Koha::SearchFilter->new({
839 query => q|{"operands":["su:biography"],"indexes":[],"operators":[]}|,
840 limits => q|{"limits":[]}|,
842 $filter_id = $filter->id;
844 ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc ) = $qb->build_query_compat( undef, undef, undef, ["search_filter:$filter_id"] );
846 is( $limit,q{(subject:biography)},"Limit correctly formed for ccl type query");
847 is( $limit_cgi,"&limit=search_filter%3A$filter_id","CGI limit is not expanded");
848 is( $limit_desc,q{(subject:biography)},"Limit description is correctly handled for ccl type query");
852 subtest "_convert_sort_fields() tests" => sub {
858 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
859 'Creating new query builder object for biblios'
862 my @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_dsc ));
866 { field => 'cn-sort', direction => 'asc' },
867 { field => 'author', direction => 'desc' }
869 'sort fields should have been split correctly'
872 # We could expect this to pass, but direction is undef instead of 'desc'
873 @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_desc ));
877 { field => 'cn-sort', direction => 'asc' },
878 { field => 'author', direction => 'desc' }
880 'sort fields should have been split correctly'
884 subtest "_sort_field() tests" => sub {
890 $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
891 'Creating new query builder object for biblios'
894 my $f = $qb->_sort_field('title');
898 'title sort mapped correctly'
901 $f = $qb->_sort_field('subject');
905 'subject sort mapped correctly'
908 $f = $qb->_sort_field('itemnumber');
912 'itemnumber sort mapped correctly'
915 $f = $qb->_sort_field('sortablenumber');
918 'sortablenumber__sort',
919 'sortablenumber sort mapped correctly'
923 $schema->storage->txn_rollback;