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