Bug 25669: (follow-up) Minor fixes
[koha-ffzg.git] / t / db_dependent / Koha / SearchEngine / Elasticsearch / QueryBuilder.t
1 #!/usr/bin/perl
2 #
3 # This file is part of Koha.
4 #
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.
9 #
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.
14 #
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>.
17
18 use Modern::Perl;
19
20 use C4::Context;
21 use Test::Exception;
22 use Test::Warn;
23 use t::lib::Mocks;
24 use t::lib::TestBuilder;
25 use Test::More tests => 7;
26
27 use List::Util qw( all );
28
29 use Koha::Database;
30 use Koha::SearchEngine::Elasticsearch::QueryBuilder;
31
32 my $schema = Koha::Database->new->schema;
33 $schema->storage->txn_begin;
34
35 my $se = Test::MockModule->new( 'Koha::SearchEngine::Elasticsearch' );
36 $se->mock( 'get_elasticsearch_mappings', sub {
37     my ($self) = @_;
38
39     my %all_mappings;
40
41     my $mappings = {
42         properties => {
43             title => {
44                 type => 'text'
45             },
46             title__sort => {
47                 type => 'text'
48             },
49             subject => {
50                 type => 'text',
51                 facet => 1
52             },
53             'subject-heading-thesaurus' => {
54                 type => 'text',
55                 facet => 1
56             },
57             itemnumber => {
58                 type => 'integer'
59             },
60             sortablenumber => {
61                 type => 'integer'
62             },
63             sortablenumber__sort => {
64                 type => 'integer'
65             },
66             heading => {
67                 type => 'text'
68             },
69             'heading-main' => {
70                 type => 'text'
71             },
72             heading__sort => {
73                 type => 'text'
74             },
75             match => {
76                 type => 'text'
77             },
78             'match-heading' => {
79                 type => 'text'
80             },
81             'match-heading-see-from' => {
82                 type => 'text'
83             },
84         }
85     };
86     $all_mappings{$self->index} = $mappings;
87
88     my $sort_fields = {
89         $self->index => {
90             title => 1,
91             subject => 0,
92             itemnumber => 0,
93             sortablenumber => 1,
94             mainentry => 1
95         }
96     };
97     $self->sort_fields($sort_fields->{$self->index});
98
99     return $all_mappings{$self->index};
100 });
101
102 subtest 'build_authorities_query_compat() tests' => sub {
103
104     plan tests => 65;
105
106     my $qb;
107
108     ok(
109         $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'authorities' }),
110         'Creating new query builder object for authorities'
111     );
112
113     my $koha_to_index_name = $Koha::SearchEngine::Elasticsearch::QueryBuilder::koha_to_index_name;
114     my $search_term = 'a';
115     foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
116         my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
117         if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
118             is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
119                 "a*");
120         } else {
121             is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
122                 "a*");
123         }
124         is( $query->{query}->{bool}->{must}[0]->{query_string}->{analyze_wildcard}, JSON::true, 'Set analyze_wildcard true' );
125         is( $query->{query}->{bool}->{must}[0]->{query_string}->{lenient}, JSON::true, 'Set lenient true' );
126     }
127
128     $search_term = 'Donald Duck';
129     foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
130         my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
131         is( $query->{query}->{bool}->{must}[0]->{query_string}->{query}, "(Donald*) AND (Duck*)" );
132         if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
133             isa_ok( $query->{query}->{bool}->{must}[0]->{query_string}->{fields}, 'ARRAY')
134         } else {
135             is( $query->{query}->{bool}->{must}[0]->{query_string}->{default_field}, $koha_to_index_name->{$koha_name} );
136         }
137     }
138
139     foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
140         my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['is'], [$search_term], 'AUTH_TYPE', 'asc' );
141         if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
142             is(
143                 $query->{query}->{bool}->{must}[0]->{multi_match}->{query},
144                 "Donald Duck"
145             );
146             my $all_matches = all { /\.ci_raw$/ }
147                 @{$query->{query}->{bool}->{must}[0]->{multi_match}->{fields}};
148             ok( $all_matches, 'Correct fields parameter for "is" query in "any" or "all"' );
149         } else {
150             is(
151                 $query->{query}->{bool}->{must}[0]->{term}->{$koha_to_index_name->{$koha_name} . ".ci_raw"},
152                 "Donald Duck"
153             );
154         }
155     }
156
157     foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
158         my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'asc' );
159         if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
160             my $all_matches = all { (%{$_->{prefix}})[0] =~ /\.ci_raw$/ && (%{$_->{prefix}})[1] eq "Donald Duck" }
161                 @{$query->{query}->{bool}->{must}[0]->{bool}->{should}};
162             ok( $all_matches, "Correct multiple prefix query" );
163         } else {
164             is( $query->{query}->{bool}->{must}[0]->{prefix}->{$koha_to_index_name->{$koha_name} . ".ci_raw"}, "Donald Duck" );
165         }
166     }
167
168     # Sorting
169     my $query = $qb->build_authorities_query_compat( [ 'mainentry' ],  undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingAsc' );
170     is_deeply(
171         $query->{sort},
172         [
173             {
174                 'heading__sort' => 'asc'
175             }
176         ],
177         "ascending sort parameter properly formed"
178     );
179     $query = $qb->build_authorities_query_compat( [ 'mainentry' ],  undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'HeadingDsc' );
180     is_deeply(
181         $query->{sort},
182         [
183             {
184                 'heading__sort' => 'desc'
185             }
186         ],
187         "descending sort parameter properly formed"
188     );
189
190     # Authorities type
191     $query = $qb->build_authorities_query_compat( [ 'mainentry' ],  undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
192     is_deeply(
193         $query->{query}->{bool}->{filter},
194         { term => { 'authtype.raw' => 'AUTH_TYPE' } },
195         "authorities type code is used as filter"
196     );
197
198     # Authorities marclist check
199     warning_like {
200         $query = $qb->build_authorities_query_compat( [ 'tomas','mainentry' ],  undef, undef, ['contains'], [$search_term,$search_term], 'AUTH_TYPE', 'asc' )
201     }
202     qr/Unknown search field tomas/,
203     "Warning for unknown field in marclist";
204     is_deeply(
205         $query->{query}->{bool}->{must}[0]->{query_string}->{default_field},
206         'tomas',
207         "If no mapping for marclist the index is passed through as defined"
208     );
209     is_deeply(
210         $query->{query}->{bool}->{must}[1]->{query_string}{default_field},
211         'heading',
212         "If mapping found for marclist the index is passed through converted"
213     );
214
215 };
216
217 subtest 'build_query tests' => sub {
218     plan tests => 57;
219
220     my $qb;
221
222     ok(
223         $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
224         'Creating new query builder object for biblios'
225     );
226
227     my @sort_by = 'title_asc';
228     my @sort_params = $qb->_convert_sort_fields(@sort_by);
229     my %options;
230     $options{sort} = \@sort_params;
231     my $query = $qb->build_query('test', %options);
232
233     is_deeply(
234         $query->{sort},
235         [
236             {
237             'title__sort' => {
238                     'order' => 'asc'
239                 }
240             }
241         ],
242         "sort parameter properly formed"
243     );
244
245     t::lib::Mocks::mock_preference('FacetMaxCount','37');
246     t::lib::Mocks::mock_preference('DisplayLibraryFacets','both');
247     $query = $qb->build_query('test', %options);
248     ok( defined $query->{aggregations}{ccode}{terms}{size},'we need to ask for a size or we get only 5 facet' );
249     is( $query->{aggregations}{ccode}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount');
250     is( $query->{aggregations}{homebranch}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount for homebranch');
251     is( $query->{aggregations}{holdingbranch}{terms}{size}, 37,'we ask for the size as defined by the syspref FacetMaxCount for holdingbranch');
252
253     t::lib::Mocks::mock_preference('DisplayLibraryFacets','both');
254     $query = $qb->build_query();
255     ok( defined $query->{aggregations}{homebranch},
256         'homebranch added to facets if DisplayLibraryFacets=both' );
257     ok( defined $query->{aggregations}{holdingbranch},
258         'holdingbranch added to facets if DisplayLibraryFacets=both' );
259     t::lib::Mocks::mock_preference('DisplayLibraryFacets','holding');
260     $query = $qb->build_query();
261     ok( !defined $query->{aggregations}{homebranch},
262         'homebranch not added to facets if DisplayLibraryFacets=holding' );
263     ok( defined $query->{aggregations}{holdingbranch},
264         'holdingbranch added to facets if DisplayLibraryFacets=holding' );
265     t::lib::Mocks::mock_preference('DisplayLibraryFacets','home');
266     $query = $qb->build_query();
267     ok( defined $query->{aggregations}{homebranch},
268         'homebranch added to facets if DisplayLibraryFacets=home' );
269     ok( !defined $query->{aggregations}{holdingbranch},
270         'holdingbranch not added to facets if DisplayLibraryFacets=home' );
271
272     t::lib::Mocks::mock_preference( 'QueryAutoTruncate', '' );
273
274     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
275     is(
276         $query->{query}{query_string}{query},
277         "(donald duck)",
278         "query not altered if QueryAutoTruncate disabled"
279     );
280
281     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['kw,phr'] );
282     is(
283         $query->{query}{query_string}{query},
284         '("donald duck")',
285         "keyword as phrase correctly quotes search term and strips index"
286     );
287
288     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
289     is(
290         $query->{query}{query_string}{query},
291         '(title:(donald duck))',
292         'multiple words in a query term are enclosed in parenthesis'
293     );
294
295     ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
296     is(
297         $query->{query}{query_string}{query},
298         '(title:(donald duck)) AND (author:disney)',
299         'multiple query terms are enclosed in parenthesis while a single one is not'
300     );
301
302     my ($simple_query, $query_cgi, $query_desc);
303     ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['"donald duck"', 'walt disney'], ['ti', 'au'] );
304     is($query_cgi, 'idx=ti&q=%22donald%20duck%22&idx=au&q=walt%20disney', 'query cgi ok for multiterm query');
305     is($query_desc, '(title:("donald duck")) (author:(walt disney))', 'query desc ok for multiterm query');
306
307     ( undef, $query ) = $qb->build_query_compat( undef, ['2019'], ['yr,st-year'] );
308     is(
309         $query->{query}{query_string}{query},
310         '(date-of-publication:2019)',
311         'Year in an st-year search is handled properly'
312     );
313
314     ( undef, $query ) = $qb->build_query_compat( undef, ['2018-2019'], ['yr,st-year'] );
315     is(
316         $query->{query}{query_string}{query},
317         '(date-of-publication:[2018 TO 2019])',
318         'Year range in an st-year search is handled properly'
319     );
320
321     ( undef, $query ) = $qb->build_query_compat( undef, ['-2019'], ['yr,st-year'] );
322     is(
323         $query->{query}{query_string}{query},
324         '(date-of-publication:[* TO 2019])',
325         'Open start year in year range of an st-year search is handled properly'
326     );
327
328     ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'] );
329     is(
330         $query->{query}{query_string}{query},
331         '(date-of-publication:[2019 TO *])',
332         'Open end year in year range of an st-year search is handled properly'
333     );
334
335     ( undef, $query ) = $qb->build_query_compat( undef, ['2019-'], ['yr,st-year'], ['yr,st-numeric=-2019'] );
336     is(
337         $query->{query}{query_string}{query},
338         '(date-of-publication:[2019 TO *]) AND date-of-publication:[* TO 2019]',
339         'Open end year in year range of an st-year search is handled properly'
340     );
341
342     # Enable auto-truncation
343     t::lib::Mocks::mock_preference( 'QueryAutoTruncate', '1' );
344
345     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'] );
346     is(
347         $query->{query}{query_string}{query},
348         "(donald* duck*)",
349         "simple query is auto truncated when QueryAutoTruncate enabled"
350     );
351
352     # Ensure reserved words are not truncated
353     ( undef, $query ) = $qb->build_query_compat( undef,
354         ['donald or duck and mickey not mouse'] );
355     is(
356         $query->{query}{query_string}{query},
357         "(donald* or duck* and mickey* not mouse*)",
358         "reserved words are not affected by QueryAutoTruncate"
359     );
360
361     ( undef, $query ) = $qb->build_query_compat( undef, ['donald* duck*'] );
362     is(
363         $query->{query}{query_string}{query},
364         "(donald* duck*)",
365         "query with '*' is unaltered when QueryAutoTruncate is enabled"
366     );
367
368     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck and the mouse'] );
369     is(
370         $query->{query}{query_string}{query},
371         "(donald* duck* and the* mouse*)",
372         "individual words are all truncated and stopwords ignored"
373     );
374
375     ( undef, $query ) = $qb->build_query_compat( undef, ['*'] );
376     is(
377         $query->{query}{query_string}{query},
378         "(*)",
379         "query of just '*' is unaltered when QueryAutoTruncate is enabled"
380     );
381
382     ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck"'], undef, ['available'] );
383     is(
384         $query->{query}{query_string}{query},
385         '("donald duck") AND onloan:false',
386         "query with quotes is unaltered when QueryAutoTruncate is enabled"
387     );
388
389
390     ( undef, $query ) = $qb->build_query_compat( undef, ['"donald duck" and "the mouse"'] );
391     is(
392         $query->{query}{query_string}{query},
393         '("donald duck" and "the mouse")',
394         "all quoted strings are unaltered if more than one in query"
395     );
396
397     ( undef, $query ) = $qb->build_query_compat( undef, ['barcode:123456'] );
398     is(
399         $query->{query}{query_string}{query},
400         '(barcode:123456*)',
401         "query of specific field is truncated"
402     );
403
404     ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number:"123456"'] );
405     is(
406         $query->{query}{query_string}{query},
407         '(local-number:"123456")',
408         "query of specific field including hyphen and quoted is not truncated, field name is converted to lower case"
409     );
410
411     ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number:123456'] );
412     is(
413         $query->{query}{query_string}{query},
414         '(local-number:123456*)',
415         "query of specific field including hyphen and not quoted is truncated, field name is converted to lower case"
416     );
417
418     ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number.raw:123456'] );
419     is(
420         $query->{query}{query_string}{query},
421         '(local-number.raw:123456*)',
422         "query of specific field including period and not quoted is truncated, field name is converted to lower case"
423     );
424
425     ( undef, $query ) = $qb->build_query_compat( undef, ['Local-number.raw:"123456"'] );
426     is(
427         $query->{query}{query_string}{query},
428         '(local-number.raw:"123456")',
429         "query of specific field including period and quoted is not truncated, field name is converted to lower case"
430     );
431
432     ( undef, $query ) = $qb->build_query_compat( undef, ['J.R.R'] );
433     is(
434         $query->{query}{query_string}{query},
435         '(J.R.R*)',
436         "query including period is truncated but not split at periods"
437     );
438
439     ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'] );
440     is(
441         $query->{query}{query_string}{query},
442         '(title:"donald duck")',
443         "query of specific field is not truncated when surrounded by quotes"
444     );
445
446     ( undef, $query ) = $qb->build_query_compat( undef, ['donald duck'], ['title'] );
447     is(
448         $query->{query}{query_string}{query},
449         '(title:(donald* duck*))',
450         'words of a multi-word term are properly truncated'
451     );
452
453     ( undef, $query ) = $qb->build_query_compat( ['AND'], ['donald duck', 'disney'], ['title', 'author'] );
454     is(
455         $query->{query}{query_string}{query},
456         '(title:(donald* duck*)) AND (author:disney*)',
457         'words of a multi-word term and single-word term are properly truncated'
458     );
459
460     ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress => 1 } );
461     is(
462         $query->{query}{query_string}{query},
463         '(title:"donald duck") AND suppress:false',
464         "query of specific field is added AND suppress:false"
465     );
466
467     ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef, undef, undef, undef, { suppress => 0 } );
468     is(
469         $query->{query}{query_string}{query},
470         '(title:"donald duck")',
471         "query of specific field is not added AND suppress:0"
472     );
473
474     ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan'] );
475     is(
476         $query->{query}{query_string}{query},
477         '(title:"donald duck") AND author:("Dillinger Escaplan")',
478         "Simple query with limit term quoted in parentheses"
479     );
480
481     ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan', 'itype:BOOK'] );
482     is(
483         $query->{query}{query_string}{query},
484         '(title:"donald duck") AND (author:("Dillinger Escaplan")) AND (itype:("BOOK"))',
485         "Simple query with each limit's term quoted in parentheses"
486     );
487     is($query_cgi, 'idx=&q=title%3A%22donald%20duck%22', 'query cgi');
488     is($query_desc, 'title:"donald duck"', 'query desc ok');
489
490     ( undef, $query ) = $qb->build_query_compat( ['AND'], ['title:"donald duck"'], undef, ['author:Dillinger Escaplan', 'mc-itype,phr:BOOK', 'mc-itype,phr:CD'] );
491     is(
492         $query->{query}{query_string}{query},
493         '(title:"donald duck") AND (author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))',
494         "Limits quoted correctly when passed as phrase"
495     );
496
497     # Scan queries
498     ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['new'], ['au'], undef, undef, 1 );
499     is(
500         $query->{query}{query_string}{query},
501         '*',
502         "scan query is properly formed"
503     );
504     is_deeply(
505         $query->{aggregations}{'author'}{'terms'},
506         {
507             field => 'author__facet',
508             order => { '_key' => 'asc' },
509             include => '[nN][eE][wW].*'
510         },
511         "scan aggregation request is properly formed"
512     );
513     is($query_cgi, 'idx=au&q=new&scan=1', 'query cgi');
514     is($query_desc, 'new', 'query desc ok');
515
516     ( undef, $query, $simple_query, $query_cgi, $query_desc ) = $qb->build_query_compat( undef, ['new'], [], undef, undef, 1 );
517     is(
518         $query->{query}{query_string}{query},
519         '*',
520         "scan query is properly formed"
521     );
522     is_deeply(
523         $query->{aggregations}{'subject'}{'terms'},
524         {
525             field => 'subject__facet',
526             order => { '_key' => 'asc' },
527             include => '[nN][eE][wW].*'
528         },
529         "scan aggregation request is properly formed"
530     );
531     is($query_cgi, 'idx=&q=new&scan=1', 'query cgi');
532     is($query_desc, 'new', 'query desc ok');
533
534     my( $limit, $limit_cgi, $limit_desc );
535     ( 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'] );
536     is( $limit, '(author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))', "Limit formed correctly when no search terms");
537     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");
538     is( $limit_desc,'(author:("Dillinger Escaplan")) AND itype:(("BOOK") OR ("CD"))',"Limit desc formed correctly when no search terms");
539 };
540
541
542 subtest 'build query from form subtests' => sub {
543     plan tests => 5;
544
545     my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'authorities' }),
546     #when searching for authorities from a record the form returns marclist with blanks for unentered terms
547     my @marclist = ('mainmainentry','mainentry','match', 'all');
548     my @values   = ( undef,         'Hamilton',  undef,   undef);
549     my @operator = ( 'contains', 'contains', 'contains', 'contains');
550
551     my $query = $qb->build_authorities_query_compat( \@marclist, undef,
552                     undef, \@operator , \@values, 'AUTH_TYPE', 'asc' );
553     is($query->{query}->{bool}->{must}[0]->{query_string}->{query}, "Hamilton*","Expected search is populated");
554     is( scalar @{ $query->{query}->{bool}->{must} }, 1,"Only defined search is populated");
555
556     @values[2] = 'Jefferson';
557     $query = $qb->build_authorities_query_compat( \@marclist, undef,
558                     undef, \@operator , \@values, 'AUTH_TYPE', 'asc' );
559     is($query->{query}->{bool}->{must}[0]->{query_string}->{query}, "Hamilton*","First index searched as expected");
560     is($query->{query}->{bool}->{must}[1]->{query_string}->{query}, "Jefferson*","Second index searched when populated");
561     is( scalar @{ $query->{query}->{bool}->{must} }, 2,"Only defined searches are populated");
562
563
564 };
565
566 subtest 'build_query with weighted fields tests' => sub {
567     plan tests => 6;
568
569     $se->mock( '_load_elasticsearch_mappings', sub {
570         return {
571             authorities => {
572                 Heading => {
573                     label => 'heading',
574                     type => 'string',
575                     opac => 0,
576                     staff_client => 1,
577                     mappings => [{
578                         marc_field => '150',
579                         marc_type => 'marc21',
580                     }]
581                 },
582                 Headingmain => {
583                     label => 'headingmain',
584                     type => 'string',
585                     opac => 1,
586                     staff_client => 1,
587                     mappings => [{
588                         marc_field => '150',
589                         marc_type => 'marc21',
590                     }]
591                 }
592             },
593             biblios => {
594                 abstract => {
595                     label => 'abstract',
596                     type => 'string',
597                     opac => 1,
598                     staff_client => 0,
599                     mappings => [{
600                         marc_field => '520',
601                         marc_type => 'marc21',
602                     }]
603                 },
604                 acqdate => {
605                     label => 'acqdate',
606                     type => 'string',
607                     opac => 0,
608                     staff_client => 1,
609                     mappings => [{
610                         marc_field => '952d',
611                         marc_type => 'marc21',
612                         search => 0,
613                     }, {
614                         marc_field => '9955',
615                         marc_type => 'marc21',
616                         search => 0,
617                     }]
618                 },
619                 title => {
620                     label => 'title',
621                     type => 'string',
622                     opac => 0,
623                     staff_client => 1,
624                     mappings => [{
625                         marc_field => '130',
626                         marc_type => 'marc21'
627                     }]
628                 },
629                 subject => {
630                     label => 'subject',
631                     type => 'string',
632                     opac => 0,
633                     staff_client => 1,
634                     mappings => [{
635                         marc_field => '600a',
636                         marc_type => 'marc21'
637                     }]
638                 }
639             }
640         };
641     });
642
643     my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'biblios' } );
644     Koha::SearchFields->search({})->delete;
645     Koha::SearchEngine::Elasticsearch->reset_elasticsearch_mappings();
646
647     my $search_field;
648     $search_field = Koha::SearchFields->find({ name => 'title' });
649     $search_field->update({ weight => 25.0 });
650     $search_field = Koha::SearchFields->find({ name => 'subject' });
651     $search_field->update({ weight => 15.5 });
652     Koha::SearchEngine::Elasticsearch->clear_search_fields_cache();
653
654     my ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
655     undef, undef, undef, { weighted_fields => 1 });
656
657     my $fields = $query->{query}{query_string}{fields};
658
659     is(@{$fields}, 2, 'Search field with no searchable mappings has been excluded');
660
661     my @found = grep { $_ eq 'title^25.00' } @{$fields};
662     is(@found, 1, 'Search field title has correct weight');
663
664     @found = grep { $_ eq 'subject^15.50' } @{$fields};
665     is(@found, 1, 'Search field subject has correct weight');
666
667     ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
668     undef, undef, undef, { weighted_fields => 1, is_opac => 1 });
669
670     $fields = $query->{query}{query_string}{fields};
671
672     is_deeply(
673         $fields,
674         ['abstract'],
675         'Only OPAC search fields are used when opac search is performed'
676     );
677
678     $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'authorities' } );
679     ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
680     undef, undef, undef, { weighted_fields => 1 });
681     $fields = $query->{query}{query_string}{fields};
682     is_deeply( [sort @$fields], ['heading','headingmain'],'Authorities fields retrieve for authorities index');
683
684     ( undef, $query ) = $qb->build_query_compat( undef, ['title:"donald duck"'], undef, undef,
685     undef, undef, undef, { weighted_fields => 1, is_opac => 1 });
686     $fields = $query->{query}{query_string}{fields};
687     is_deeply($fields,['headingmain'],'Only opac authorities fields retrieved for authorities index is is_opac');
688
689 };
690
691 subtest 'build_query_compat() SearchLimitLibrary tests' => sub {
692
693     plan tests => 18;
694
695     $schema->storage->txn_begin;
696
697     my $builder = t::lib::TestBuilder->new;
698
699     my $branch_1 = $builder->build_object({ class => 'Koha::Libraries' });
700     my $branch_2 = $builder->build_object({ class => 'Koha::Libraries' });
701     my $group    = $builder->build_object({ class => 'Koha::Library::Groups', value => {
702             ft_search_groups_opac => 1,
703             ft_search_groups_staff => 1,
704             parent_id => undef,
705             branchcode => undef
706         }
707     });
708     my $group_1  = $builder->build_object({ class => 'Koha::Library::Groups', value => {
709             parent_id => $group->id,
710             branchcode => $branch_1->id
711         }
712     });
713     my $group_2  = $builder->build_object({ class => 'Koha::Library::Groups', value => {
714             parent_id => $group->id,
715             branchcode => $branch_2->id
716         }
717     });
718     my $groupid = $group->id;
719     my @branchcodes = sort { $a cmp $b } ( $branch_1->id, $branch_2->id );
720
721
722     my $query_builder = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
723     t::lib::Mocks::mock_preference('SearchLimitLibrary', 'both');
724     my ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
725         $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
726     is( $limit, '(homebranch: "CPL" OR holdingbranch: "CPL")', "Branch limit expanded to home/holding branch");
727     is( $limit_desc, '(homebranch: "CPL" OR holdingbranch: "CPL")', "Limit description correctly expanded");
728     is( $limit_cgi, '&limit=branch%3ACPL', "Limit cgi does not get expanded");
729     ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
730         $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
731     is( $limit, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\" OR holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Multibranch limit expanded to home/holding branches");
732     is( $limit_desc, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\" OR holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Multibranch limit description correctly expanded");
733     is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Multibranch limit cgi does not get expanded");
734
735     t::lib::Mocks::mock_preference('SearchLimitLibrary', 'homebranch');
736     ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
737         $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
738     is( $limit, "(homebranch: \"CPL\")", "branch limit expanded to home branch");
739     is( $limit_desc, "(homebranch: \"CPL\")", "limit description correctly expanded");
740     is( $limit_cgi, "&limit=branch%3ACPL", "limit cgi does not get expanded");
741     ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
742         $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
743     is( $limit, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\")", "branch limit expanded to home branch");
744     is( $limit_desc, "(homebranch: \"$branchcodes[0]\" OR homebranch: \"$branchcodes[1]\")", "limit description correctly expanded");
745     is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Limit cgi does not get expanded");
746
747     t::lib::Mocks::mock_preference('SearchLimitLibrary', 'holdingbranch');
748     ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
749         $query_builder->build_query_compat( undef, undef, undef, [ "branch:CPL" ], undef, undef, undef, undef );
750     is( $limit, "(holdingbranch: \"CPL\")", "branch limit expanded to holding branch");
751     is( $limit_desc, "(holdingbranch: \"CPL\")", "Limit description correctly expanded");
752     is( $limit_cgi, "&limit=branch%3ACPL", "Limit cgi does not get expanded");
753     ( undef, undef, undef, undef, undef, $limit, $limit_cgi, $limit_desc, undef ) =
754         $query_builder->build_query_compat( undef, undef, undef, [ "multibranchlimit:$groupid" ], undef, undef, undef, undef );
755     is( $limit, "(holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "branch limit expanded to holding branch");
756     is( $limit_desc, "(holdingbranch: \"$branchcodes[0]\" OR holdingbranch: \"$branchcodes[1]\")", "Limit description correctly expanded");
757     is( $limit_cgi, "&limit=multibranchlimit%3A$groupid", "Limit cgi does not get expanded");
758
759 };
760
761 subtest "_convert_sort_fields() tests" => sub {
762     plan tests => 3;
763
764     my $qb;
765
766     ok(
767         $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
768         'Creating new query builder object for biblios'
769     );
770
771     my @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_dsc ));
772     is_deeply(
773         \@sort_by,
774         [
775             { field => 'cn-sort', direction => 'asc' },
776             { field => 'author',  direction => 'desc' }
777         ],
778         'sort fields should have been split correctly'
779     );
780
781     # We could expect this to pass, but direction is undef instead of 'desc'
782     @sort_by = $qb->_convert_sort_fields(qw( call_number_asc author_desc ));
783     is_deeply(
784         \@sort_by,
785         [
786             { field => 'cn-sort', direction => 'asc' },
787             { field => 'author',  direction => 'desc' }
788         ],
789         'sort fields should have been split correctly'
790     );
791 };
792
793 subtest "_sort_field() tests" => sub {
794     plan tests => 5;
795
796     my $qb;
797
798     ok(
799         $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new({ 'index' => 'biblios' }),
800         'Creating new query builder object for biblios'
801     );
802
803     my $f = $qb->_sort_field('title');
804     is(
805         $f,
806         'title__sort',
807         'title sort mapped correctly'
808     );
809
810     $f = $qb->_sort_field('subject');
811     is(
812         $f,
813         'subject.raw',
814         'subject sort mapped correctly'
815     );
816
817     $f = $qb->_sort_field('itemnumber');
818     is(
819         $f,
820         'itemnumber',
821         'itemnumber sort mapped correctly'
822     );
823
824     $f = $qb->_sort_field('sortablenumber');
825     is(
826         $f,
827         'sortablenumber__sort',
828         'sortablenumber sort mapped correctly'
829     );
830 };
831
832 $schema->storage->txn_rollback;