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