Bug 24807: Refactor using tokenize_callbacks
[koha.git] / t / db_dependent / Koha / SearchEngine / Elasticsearch.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 Test::More tests => 6;
21 use Test::Exception;
22
23 use t::lib::Mocks;
24 use t::lib::TestBuilder;
25
26 use Test::MockModule;
27
28 use MARC::Record;
29 use Try::Tiny;
30 use List::Util qw( any );
31
32 use Koha::SearchEngine::Elasticsearch;
33 use Koha::SearchEngine::Elasticsearch::Search;
34
35 my $schema = Koha::Database->new->schema;
36 $schema->storage->txn_begin;
37
38 subtest '_read_configuration() tests' => sub {
39
40     plan tests => 13;
41
42     my $configuration;
43     t::lib::Mocks::mock_config( 'elasticsearch', undef );
44
45     # 'elasticsearch' missing in configuration
46     throws_ok {
47         $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
48     }
49     'Koha::Exceptions::Config::MissingEntry',
50       'Configuration problem, exception thrown';
51     is(
52         $@->message,
53         "Missing <elasticsearch> entry in koha-conf.xml",
54         'Exception message is correct'
55     );
56
57     # 'elasticsearch' present but no 'server' entry
58     t::lib::Mocks::mock_config( 'elasticsearch', {} );
59     throws_ok {
60         $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
61     }
62     'Koha::Exceptions::Config::MissingEntry',
63       'Configuration problem, exception thrown';
64     is(
65         $@->message,
66         "Missing <elasticsearch>/<server> entry in koha-conf.xml",
67         'Exception message is correct'
68     );
69
70     # 'elasticsearch' and 'server' entries present, but no 'index_name'
71     t::lib::Mocks::mock_config( 'elasticsearch', { server => 'a_server' } );
72     throws_ok {
73         $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
74     }
75     'Koha::Exceptions::Config::MissingEntry',
76       'Configuration problem, exception thrown';
77     is(
78         $@->message,
79         "Missing <elasticsearch>/<index_name> entry in koha-conf.xml",
80         'Exception message is correct'
81     );
82
83     # Correct configuration, only one server
84     t::lib::Mocks::mock_config( 'elasticsearch',  { server => 'a_server', index_name => 'index' } );
85
86     $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
87     is( $configuration->{index_name}, 'index', 'Index configuration parsed correctly' );
88     is_deeply( $configuration->{nodes}, ['a_server'], 'Server configuration parsed correctly' );
89
90     # Correct configuration, two servers
91     my @servers = ('a_server', 'another_server');
92     t::lib::Mocks::mock_config( 'elasticsearch', { server => \@servers, index_name => 'index' } );
93
94     $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
95     is( $configuration->{index_name}, 'index', 'Index configuration parsed correctly' );
96     is( $configuration->{cxn_pool}, 'Static', 'cxn_pool configuration set correctly to Static if not specified' );
97     is_deeply( $configuration->{nodes}, \@servers , 'Server configuration parsed correctly' );
98
99     t::lib::Mocks::mock_config( 'elasticsearch', { server => \@servers, index_name => 'index', cxn_pool => 'Fluid' } );
100
101     $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
102     is( $configuration->{cxn_pool}, 'Fluid', 'cxn_pool configuration parsed correctly' );
103
104     my $params = Koha::SearchEngine::Elasticsearch::get_elasticsearch_params;
105     is_deeply( $configuration->{nodes}, \@servers , 'get_elasticsearch_params is just a wrapper for _read_configuration' );
106
107 };
108
109 subtest 'get_elasticsearch_settings() tests' => sub {
110
111     plan tests => 1;
112
113     my $settings;
114
115     # test reading index settings
116     my $es = Koha::SearchEngine::Elasticsearch->new( {index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX} );
117     $settings = $es->get_elasticsearch_settings();
118     is( $settings->{index}{analysis}{analyzer}{analyzer_phrase}{tokenizer}, 'keyword', 'Index settings parsed correctly' );
119 };
120
121 subtest 'get_elasticsearch_mappings() tests' => sub {
122
123     plan tests => 1;
124
125     my $mappings;
126
127     # test reading mappings
128     my $es = Koha::SearchEngine::Elasticsearch->new( {index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX} );
129     $mappings = $es->get_elasticsearch_mappings();
130     is( $mappings->{data}{properties}{isbn__sort}{index}, 'false', 'Field mappings parsed correctly' );
131 };
132
133 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests' => sub {
134
135     plan tests => 58;
136
137     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
138     t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ISO2709');
139
140     my @mappings = (
141         {
142             name => 'control_number',
143             type => 'string',
144             facet => 0,
145             suggestible => 0,
146             searchable => 1,
147             sort => undef,
148             marc_type => 'marc21',
149             marc_field => '001',
150         },
151         {
152             name => 'isbn',
153             type => 'isbn',
154             facet => 0,
155             suggestible => 0,
156             searchable => 1,
157             sort => 0,
158             marc_type => 'marc21',
159             marc_field => '020a',
160         },
161         {
162             name => 'author',
163             type => 'string',
164             facet => 1,
165             suggestible => 1,
166             searchable => 1,
167             sort => undef,
168             marc_type => 'marc21',
169             marc_field => '100a',
170         },
171         {
172             name => 'author',
173             type => 'string',
174             facet => 1,
175             suggestible => 1,
176             searchable => 1,
177             sort => 1,
178             marc_type => 'marc21',
179             marc_field => '110a',
180         },
181         {
182             name => 'title',
183             type => 'string',
184             facet => 0,
185             suggestible => 1,
186             searchable => 1,
187             sort => 1,
188             marc_type => 'marc21',
189             marc_field => '245(ab)ab',
190         },
191         {
192             name => 'unimarc_title',
193             type => 'string',
194             facet => 0,
195             suggestible => 1,
196             searchable => 1,
197             sort => 1,
198             marc_type => 'unimarc',
199             marc_field => '245a',
200         },
201         {
202             name => 'title',
203             type => 'string',
204             facet => 0,
205             suggestible => undef,
206             searchable => 1,
207             sort => 0,
208             marc_type => 'marc21',
209             marc_field => '220',
210         },
211         {
212             name => 'uniform_title',
213             type => 'string',
214             facet => 0,
215             suggestible => 0,
216             searchable => 1,
217             sort => 1,
218             marc_type => 'marc21',
219             marc_field => '240a',
220         },
221         {
222             name => 'title_wildcard',
223             type => 'string',
224             facet => 0,
225             suggestible => 0,
226             searchable => 1,
227             sort => undef,
228             marc_type => 'marc21',
229             marc_field => '245',
230         },
231         {
232             name => 'sum_item_price',
233             type => 'sum',
234             facet => 0,
235             suggestible => 0,
236             searchable => 1,
237             sort => 0,
238             marc_type => 'marc21',
239             marc_field => '952g',
240         },
241         {
242             name => 'items_withdrawn_status',
243             type => 'boolean',
244             facet => 0,
245             suggestible => 0,
246             searchable => 1,
247             sort => 0,
248             marc_type => 'marc21',
249             marc_field => '9520',
250         },
251         {
252             name => 'local_classification',
253             type => 'string',
254             facet => 0,
255             suggestible => 0,
256             searchable => 1,
257             sort => 1,
258             marc_type => 'marc21',
259             marc_field => '952o',
260         },
261         {
262             name => 'type_of_record',
263             type => 'string',
264             facet => 0,
265             suggestible => 0,
266             searchable => 1,
267             sort => 0,
268             marc_type => 'marc21',
269             marc_field => 'leader_/6',
270         },
271         {
272             name => 'type_of_record_and_bib_level',
273             type => 'string',
274             facet => 0,
275             suggestible => 0,
276             searchable => 1,
277             sort => 0,
278             marc_type => 'marc21',
279             marc_field => 'leader_/6-7',
280         },
281         {
282             name => 'ff7-00',
283             type => 'string',
284             facet => 0,
285             suggestible => 0,
286             searchable => 1,
287             sort => 0,
288             marc_type => 'marc21',
289             marc_field => '007_/0',
290         },
291         {
292             name => 'issues',
293             type => 'sum',
294             facet => 0,
295             suggestible => 0,
296             searchable => 1,
297             sort => 1,
298             marc_type => 'marc21',
299             marc_field => '952l',
300           },
301           {
302             name => 'copydate',
303             type => 'year',
304             facet => 0,
305             suggestible => 0,
306             searchable => 1,
307             sort => 1,
308             marc_type => 'marc21',
309             marc_field => '260c',
310           },
311           {
312             name => 'date-of-publication',
313             type => 'year',
314             facet => 0,
315             suggestible => 0,
316             searchable => 1,
317             sort => 1,
318             marc_type => 'marc21',
319             marc_field => '008_/7-10',
320         },
321
322     );
323
324     my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
325     $se->mock('_foreach_mapping', sub {
326         my ($self, $sub) = @_;
327
328         foreach my $map (@mappings) {
329             $sub->(
330                 $map->{name},
331                 $map->{type},
332                 $map->{facet},
333                 $map->{suggestible},
334                 $map->{sort},
335                 $map->{searchable},
336                 $map->{marc_type},
337                 $map->{marc_field}
338             );
339         }
340     });
341
342     my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
343
344     my $callno = 'ABC123';
345     my $callno2 = 'ABC456';
346     my $long_callno = '1234567890' x 30;
347
348     my $marc_record_1 = MARC::Record->new();
349     $marc_record_1->leader('     cam  22      a 4500');
350     $marc_record_1->append_fields(
351         MARC::Field->new('001', '123'),
352         MARC::Field->new('007', 'ku'),
353         MARC::Field->new('008', '901111s1962 xxk|||| |00| ||eng c'),
354         MARC::Field->new('020', '', '', a => '1-56619-909-3'),
355         MARC::Field->new('100', '', '', a => 'Author 1'),
356         MARC::Field->new('110', '', '', a => 'Corp Author'),
357         MARC::Field->new('210', '', '', a => 'Title 1'),
358         MARC::Field->new('240', '', '4', a => 'The uniform title with nonfiling indicator'),
359         MARC::Field->new('245', '', '', a => 'Title:', b => 'first record'),
360         MARC::Field->new('260', '', '', a => 'New York :', b => 'Ace ,', c => 'c1962'),
361         MARC::Field->new('999', '', '', c => '1234567'),
362         # '  ' for testing trimming of white space in boolean value callback:
363         MARC::Field->new('952', '', '', 0 => '  ', g => '123.30', o => $callno, l => 3),
364         MARC::Field->new('952', '', '', 0 => 0, g => '127.20', o => $callno2, l => 2),
365         MARC::Field->new('952', '', '', 0 => 1, g => '0.00', o => $long_callno, l => 1),
366     );
367     my $marc_record_2 = MARC::Record->new();
368     $marc_record_2->leader('     cam  22      a 4500');
369     $marc_record_2->append_fields(
370         MARC::Field->new('008', '901111s19uu xxk|||| |00| ||eng c'),
371         MARC::Field->new('100', '', '', a => 'Author 2'),
372         # MARC::Field->new('210', '', '', a => 'Title 2'),
373         # MARC::Field->new('245', '', '', a => 'Title: second record'),
374         MARC::Field->new('260', '', '', a => 'New York :', b => 'Ace ,', c => '1963-2003'),
375         MARC::Field->new('999', '', '', c => '1234568'),
376         MARC::Field->new('952', '', '', 0 => 1, g => 'string where should be numeric', o => $long_callno),
377     );
378     my $records = [ $marc_record_1, $marc_record_2 ];
379
380     $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
381
382     my $docs = $see->marc_records_to_documents($records);
383
384     # First record:
385     is(scalar @{$docs}, 2, 'Two records converted to documents');
386
387     is_deeply($docs->[0]->{control_number}, ['123'], 'First record control number should be set correctly');
388
389     is_deeply($docs->[0]->{'ff7-00'}, ['k'], 'First record ff7-00 should be set correctly');
390
391     is(scalar @{$docs->[0]->{author}}, 2, 'First document author field should contain two values');
392     is_deeply($docs->[0]->{author}, ['Author 1', 'Corp Author'], 'First document author field should be set correctly');
393
394     is(scalar @{$docs->[0]->{author__sort}}, 1, 'First document author__sort field should have a single value');
395     is_deeply($docs->[0]->{author__sort}, ['Author 1 Corp Author'], 'First document author__sort field should be set correctly');
396
397     is(scalar @{$docs->[0]->{title__sort}}, 1, 'First document title__sort field should have a single');
398     is_deeply($docs->[0]->{title__sort}, ['Title: first record Title: first record'], 'First document title__sort field should be set correctly');
399
400     is($docs->[0]->{issues}, 6, 'Issues field should be sum of the issues for each item');
401     is($docs->[0]->{issues__sort}, 6, 'Issues sort field should also be a sum of the issues');
402
403     is(scalar @{$docs->[0]->{title_wildcard}}, 2, 'First document title_wildcard field should have two values');
404     is_deeply($docs->[0]->{title_wildcard}, ['Title:', 'first record'], 'First document title_wildcard field should be set correctly');
405
406
407     is(scalar @{$docs->[0]->{author__suggestion}}, 2, 'First document author__suggestion field should contain two values');
408     is_deeply(
409         $docs->[0]->{author__suggestion},
410         [
411             {
412                 'input' => 'Author 1'
413             },
414             {
415                 'input' => 'Corp Author'
416             }
417         ],
418         'First document author__suggestion field should be set correctly'
419     );
420
421     is(scalar @{$docs->[0]->{title__suggestion}}, 3, 'First document title__suggestion field should contain three values');
422     is_deeply(
423         $docs->[0]->{title__suggestion},
424         [
425             { 'input' => 'Title:' },
426             { 'input' => 'first record' },
427             { 'input' => 'Title: first record' }
428         ],
429         'First document title__suggestion field should be set correctly'
430     );
431
432     ok(!(defined $docs->[0]->{title__facet}), 'First document should have no title__facet field');
433
434     is(scalar @{$docs->[0]->{author__facet}}, 2, 'First document author__facet field should have two values');
435     is_deeply(
436         $docs->[0]->{author__facet},
437         ['Author 1', 'Corp Author'],
438         'First document author__facet field should be set correctly'
439     );
440
441     is(scalar @{$docs->[0]->{items_withdrawn_status}}, 2, 'First document items_withdrawn_status field should have two values');
442     is_deeply(
443         $docs->[0]->{items_withdrawn_status},
444         ['false', 'true'],
445         'First document items_withdrawn_status field should be set correctly'
446     );
447
448     is(
449         $docs->[0]->{sum_item_price},
450         '250.5',
451         'First document sum_item_price field should be set correctly'
452     );
453
454     ok(defined $docs->[0]->{marc_data}, 'First document marc_data field should be set');
455     ok(defined $docs->[0]->{marc_format}, 'First document marc_format field should be set');
456     is($docs->[0]->{marc_format}, 'base64ISO2709', 'First document marc_format should be set correctly');
457
458     my $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
459
460     ok($decoded_marc_record->isa('MARC::Record'), "base64ISO2709 record successfully decoded from result");
461     is($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded base64ISO2709 record has same data as original record");
462
463     is(scalar @{$docs->[0]->{type_of_record}}, 1, 'First document type_of_record field should have one value');
464     is_deeply(
465         $docs->[0]->{type_of_record},
466         ['a'],
467         'First document type_of_record field should be set correctly'
468     );
469
470     is(scalar @{$docs->[0]->{type_of_record_and_bib_level}}, 1, 'First document type_of_record_and_bib_level field should have one value');
471     is_deeply(
472         $docs->[0]->{type_of_record_and_bib_level},
473         ['am'],
474         'First document type_of_record_and_bib_level field should be set correctly'
475     );
476
477     is(scalar @{$docs->[0]->{isbn}}, 4, 'First document isbn field should contain four values');
478     is_deeply($docs->[0]->{isbn}, ['978-1-56619-909-4', '9781566199094', '1-56619-909-3', '1566199093'], 'First document isbn field should be set correctly');
479
480     is_deeply(
481         $docs->[0]->{'local_classification'},
482         [$callno, $callno2, $long_callno],
483         'First document local_classification field should be set correctly'
484     );
485
486     # Nonfiling characters for sort fields
487     is_deeply(
488         $docs->[0]->{uniform_title},
489         ['The uniform title with nonfiling indicator'],
490         'First document uniform_title field should contain the title verbatim'
491     );
492     is_deeply(
493         $docs->[0]->{uniform_title__sort},
494         ['uniform title with nonfiling indicator'],
495         'First document uniform_title__sort field should contain the title with the first four initial characters removed'
496     );
497
498     # Tests for 'year' type
499     is(scalar @{$docs->[0]->{'date-of-publication'}}, 1, 'First document date-of-publication field should contain one value');
500     is_deeply($docs->[0]->{'date-of-publication'}, ['1962'], 'First document date-of-publication field should be set correctly');
501
502     is_deeply(
503       $docs->[0]->{'copydate'},
504       ['1962'],
505       'First document copydate field should be set correctly'
506     );
507
508     # Second record:
509
510     is(scalar @{$docs->[1]->{author}}, 1, 'Second document author field should contain one value');
511     is_deeply($docs->[1]->{author}, ['Author 2'], 'Second document author field should be set correctly');
512
513     is(scalar @{$docs->[1]->{items_withdrawn_status}}, 1, 'Second document items_withdrawn_status field should have one value');
514     is_deeply(
515         $docs->[1]->{items_withdrawn_status},
516         ['true'],
517         'Second document items_withdrawn_status field should be set correctly'
518     );
519
520     is(
521         $docs->[1]->{sum_item_price},
522         0,
523         'Second document sum_item_price field should be set correctly'
524     );
525
526     is_deeply(
527         $docs->[1]->{local_classification__sort},
528         [substr($long_callno, 0, 255)],
529         'Second document local_classification__sort field should be set correctly'
530     );
531
532     # Tests for 'year' type
533     is_deeply(
534       $docs->[1]->{'copydate'},
535       ['1963', '2003'],
536       'Second document copydate field should be set correctly'
537     );
538     is_deeply(
539       $docs->[1]->{'date-of-publication'},
540       ['1900'],
541       'Second document date-of-publication field should be set correctly'
542     );
543
544     # Mappings marc_type:
545
546     ok(!(defined $docs->[0]->{unimarc_title}), "No mapping when marc_type doesn't match marc flavour");
547
548     # Marc serialization format fallback for records exceeding ISO2709 max record size
549
550     my $large_marc_record = MARC::Record->new();
551     $large_marc_record->leader('     cam  22      a 4500');
552
553     $large_marc_record->append_fields(
554         MARC::Field->new('100', '', '', a => 'Author 1'),
555         MARC::Field->new('110', '', '', a => 'Corp Author'),
556         MARC::Field->new('210', '', '', a => 'Title 1'),
557         MARC::Field->new('245', '', '', a => 'Title:', b => 'large record'),
558         MARC::Field->new('999', '', '', c => '1234567'),
559     );
560
561     my $item_field = MARC::Field->new('952', '', '', o => '123456789123456789123456789', p => '123456789', z => 'test');
562     my $items_count = 1638;
563     while(--$items_count) {
564         $large_marc_record->append_fields($item_field);
565     }
566
567     $docs = $see->marc_records_to_documents([$large_marc_record]);
568
569     is($docs->[0]->{marc_format}, 'MARCXML', 'For record exceeding max record size marc_format should be set correctly');
570
571     $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
572
573     ok($decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result");
574     is($decoded_marc_record->as_xml_record(), $large_marc_record->as_xml_record(), "Decoded MARCXML record has same data as original record");
575
576     push @mappings, {
577         name => 'title',
578         type => 'string',
579         facet => 0,
580         suggestible => 1,
581         sort => 1,
582         marc_type => 'marc21',
583         marc_field => '245((ab)ab',
584     };
585
586     my $exception = try {
587         $see->marc_records_to_documents($records);
588     }
589     catch {
590         return $_;
591     };
592
593     ok(defined $exception, "Exception has been thrown when processing mapping with unmatched opening parenthesis");
594     ok($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
595     ok($exception->message =~ /Unmatched opening parenthesis/, "Exception has the correct message");
596
597     pop @mappings;
598     push @mappings, {
599         name => 'title',
600         type => 'string',
601         facet => 0,
602         suggestible => 1,
603         sort => 1,
604         marc_type => 'marc21',
605         marc_field => '245(ab))ab',
606     };
607
608     $exception = try {
609         $see->marc_records_to_documents($records);
610     }
611     catch {
612         return $_;
613     };
614
615     ok(defined $exception, "Exception has been thrown when processing mapping with unmatched closing parenthesis");
616     ok($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
617     ok($exception->message =~ /Unmatched closing parenthesis/, "Exception has the correct message");
618 };
619
620 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents_array () tests' => sub {
621
622     plan tests => 5;
623
624     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
625     t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ARRAY');
626
627     my @mappings = (
628         {
629             name => 'control_number',
630             type => 'string',
631             facet => 0,
632             suggestible => 0,
633             sort => undef,
634             searchable => 1,
635             marc_type => 'marc21',
636             marc_field => '001',
637         }
638     );
639
640     my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
641     $se->mock('_foreach_mapping', sub {
642         my ($self, $sub) = @_;
643
644         foreach my $map (@mappings) {
645             $sub->(
646                 $map->{name},
647                 $map->{type},
648                 $map->{facet},
649                 $map->{suggestible},
650                 $map->{sort},
651                 $map->{searchable},
652                 $map->{marc_type},
653                 $map->{marc_field}
654             );
655         }
656     });
657
658     my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
659
660     my $marc_record_1 = MARC::Record->new();
661     $marc_record_1->leader('     cam  22      a 4500');
662     $marc_record_1->append_fields(
663         MARC::Field->new('001', '123'),
664         MARC::Field->new('020', '', '', a => '1-56619-909-3'),
665         MARC::Field->new('100', '', '', a => 'Author 1'),
666         MARC::Field->new('110', '', '', a => 'Corp Author'),
667         MARC::Field->new('210', '', '', a => 'Title 1'),
668         MARC::Field->new('245', '', '', a => 'Title:', b => 'first record'),
669         MARC::Field->new('999', '', '', c => '1234567'),
670     );
671     my $marc_record_2 = MARC::Record->new();
672     $marc_record_2->leader('     cam  22      a 4500');
673     $marc_record_2->append_fields(
674         MARC::Field->new('100', '', '', a => 'Author 2'),
675         # MARC::Field->new('210', '', '', a => 'Title 2'),
676         # MARC::Field->new('245', '', '', a => 'Title: second record'),
677         MARC::Field->new('999', '', '', c => '1234568'),
678         MARC::Field->new('952', '', '', 0 => 1, g => 'string where should be numeric'),
679     );
680     my $records = [ $marc_record_1, $marc_record_2 ];
681
682     $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
683
684     my $docs = $see->marc_records_to_documents($records);
685
686     # First record:
687     is(scalar @{$docs}, 2, 'Two records converted to documents');
688
689     is_deeply($docs->[0]->{control_number}, ['123'], 'First record control number should be set correctly');
690
691     is($docs->[0]->{marc_format}, 'ARRAY', 'First document marc_format should be set correctly');
692
693     my $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
694
695     ok($decoded_marc_record->isa('MARC::Record'), "ARRAY record successfully decoded from result");
696     is($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded ARRAY record has same data as original record");
697 };
698
699 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () authority tests' => sub {
700
701     plan tests => 2;
702
703     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
704     t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ISO2709');
705
706     my $builder = t::lib::TestBuilder->new;
707     my $auth_type = $builder->build_object({ class => 'Koha::Authority::Types', value =>{
708             auth_tag_to_report => '150'
709         }
710     });
711
712     my @mappings = (
713         {
714             name => 'match',
715             type => 'string',
716             facet => 0,
717             suggestible => 0,
718             searchable => 1,
719             sort => 0,
720             marc_type => 'marc21',
721             marc_field => '150(ae)',
722         }
723     );
724
725     my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
726     $se->mock('_foreach_mapping', sub {
727         my ($self, $sub) = @_;
728
729         foreach my $map (@mappings) {
730             $sub->(
731                 $map->{name},
732                 $map->{type},
733                 $map->{facet},
734                 $map->{suggestible},
735                 $map->{sort},
736                 $map->{searchable},
737                 $map->{marc_type},
738                 $map->{marc_field}
739             );
740         }
741     });
742
743     my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::AUTHORITIES_INDEX });
744     my $marc_record_1 = MARC::Record->new();
745     $marc_record_1->append_fields(
746         MARC::Field->new('001', '123'),
747         MARC::Field->new('007', 'ku'),
748         MARC::Field->new('020', '', '', a => '1-56619-909-3'),
749         MARC::Field->new('150', '', '', a => 'Subject', v => 'Genresubdiv', x => 'Generalsubdiv', z => 'Geosubdiv'),
750     );
751     my $marc_record_2 = MARC::Record->new();
752     $marc_record_2->append_fields(
753         MARC::Field->new('150', '', '', a => 'Subject', v => 'Genresubdiv', z => 'Geosubdiv', x => 'Generalsubdiv', e => 'wrongsubdiv' ),
754     );
755     my $records = [ $marc_record_1, $marc_record_2 ];
756
757     $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
758
759     my $docs = $see->marc_records_to_documents($records);
760
761     ok(
762         any { $_ eq "Subject formsubdiv Genresubdiv generalsubdiv Generalsubdiv geographicsubdiv Geosubdiv" }
763         @{$docs->[0]->{'match-heading'}},
764         "First record match-heading should contain the correctly formatted heading"
765     );
766     ok(
767         any { $_ eq "Subject formsubdiv Genresubdiv geographicsubdiv Geosubdiv generalsubdiv Generalsubdiv" }
768         @{$docs->[1]->{'match-heading'}},
769         "Second record match-heading should contain the correctly formatted heading without wrong subfield"
770     );
771 };
772
773 $schema->storage->txn_rollback;