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