3 # This file is part of Koha.
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.
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.
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>.
20 use Test::More tests => 8;
24 use t::lib::TestBuilder;
30 use List::Util qw( any );
32 use C4::AuthoritiesMarc qw( AddAuthority );
35 use Koha::SearchEngine::Elasticsearch;
36 use Koha::SearchEngine::Elasticsearch::Search;
38 my $schema = Koha::Database->new->schema;
39 $schema->storage->txn_begin;
41 subtest '_read_configuration() tests' => sub {
46 t::lib::Mocks::mock_config( 'elasticsearch', undef );
48 # 'elasticsearch' missing in configuration
50 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
52 'Koha::Exceptions::Config::MissingEntry',
53 'Configuration problem, exception thrown';
56 "Missing <elasticsearch> entry in koha-conf.xml",
57 'Exception message is correct'
60 # 'elasticsearch' present but no 'server' entry
61 t::lib::Mocks::mock_config( 'elasticsearch', {} );
63 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
65 'Koha::Exceptions::Config::MissingEntry',
66 'Configuration problem, exception thrown';
69 "Missing <elasticsearch>/<server> entry in koha-conf.xml",
70 'Exception message is correct'
73 # 'elasticsearch' and 'server' entries present, but no 'index_name'
74 t::lib::Mocks::mock_config( 'elasticsearch', { server => 'a_server' } );
76 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
78 'Koha::Exceptions::Config::MissingEntry',
79 'Configuration problem, exception thrown';
82 "Missing <elasticsearch>/<index_name> entry in koha-conf.xml",
83 'Exception message is correct'
86 # Correct configuration, only one server
87 t::lib::Mocks::mock_config( 'elasticsearch', { server => 'a_server', index_name => 'index' } );
89 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
90 is( $configuration->{index_name}, 'index', 'Index configuration parsed correctly' );
91 is_deeply( $configuration->{nodes}, ['a_server'], 'Server configuration parsed correctly' );
93 # Correct configuration, two servers
94 my @servers = ('a_server', 'another_server');
95 t::lib::Mocks::mock_config( 'elasticsearch', { server => \@servers, index_name => 'index' } );
97 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
98 is( $configuration->{index_name}, 'index', 'Index configuration parsed correctly' );
99 is( $configuration->{cxn_pool}, 'Static', 'cxn_pool configuration set correctly to Static if not specified' );
100 is_deeply( $configuration->{nodes}, \@servers , 'Server configuration parsed correctly' );
102 t::lib::Mocks::mock_config( 'elasticsearch', { server => \@servers, index_name => 'index', cxn_pool => 'Sniff' } );
104 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
105 is( $configuration->{cxn_pool}, 'Sniff', 'cxn_pool configuration parsed correctly' );
106 isnt( defined $configuration->{trace_to}, 'trace_to is not defined if not set' );
108 my $params = Koha::SearchEngine::Elasticsearch::get_elasticsearch_params;
109 is_deeply( $configuration->{nodes}, \@servers , 'get_elasticsearch_params is just a wrapper for _read_configuration' );
111 t::lib::Mocks::mock_config( 'elasticsearch', { server => \@servers, index_name => 'index', cxn_pool => 'Sniff', trace_to => 'Stderr' } );
113 $configuration = Koha::SearchEngine::Elasticsearch::_read_configuration;
114 is( $configuration->{trace_to}, 'Stderr', 'trace_to configuration parsed correctly' );
117 subtest 'get_elasticsearch_settings() tests' => sub {
123 # test reading index settings
124 my $es = Koha::SearchEngine::Elasticsearch->new( {index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX} );
125 $settings = $es->get_elasticsearch_settings();
126 is( $settings->{index}{analysis}{analyzer}{analyzer_phrase}{tokenizer}, 'keyword', 'Index settings parsed correctly' );
129 subtest 'get_elasticsearch_mappings() tests' => sub {
135 # test reading mappings
136 my $es = Koha::SearchEngine::Elasticsearch->new( {index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX} );
137 $mappings = $es->get_elasticsearch_mappings();
138 is( $mappings->{properties}{isbn__sort}{index}, 'false', 'Field mappings parsed correctly' );
141 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests' => sub {
145 t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
146 t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ISO2709');
150 name => 'control_number',
156 marc_type => 'marc21',
166 marc_type => 'marc21',
167 marc_field => '020a',
176 marc_type => 'marc21',
177 marc_field => '100a',
186 marc_type => 'marc21',
187 marc_field => '110a',
196 marc_type => 'marc21',
197 marc_field => '245(ab)ab',
200 name => 'unimarc_title',
206 marc_type => 'unimarc',
207 marc_field => '245a',
213 suggestible => undef,
216 marc_type => 'marc21',
220 name => 'uniform_title',
226 marc_type => 'marc21',
227 marc_field => '240a',
230 name => 'title_wildcard',
236 marc_type => 'marc21',
240 name => 'sum_item_price',
246 marc_type => 'marc21',
247 marc_field => '952g',
250 name => 'items_withdrawn_status',
256 marc_type => 'marc21',
257 marc_field => '9520',
260 name => 'local_classification',
266 marc_type => 'marc21',
267 marc_field => '952o',
270 name => 'type_of_record',
276 marc_type => 'marc21',
277 marc_field => 'leader_/6',
280 name => 'type_of_record_and_bib_level',
286 marc_type => 'marc21',
287 marc_field => 'leader_/6-7',
296 marc_type => 'marc21',
297 marc_field => '007_/0',
306 marc_type => 'marc21',
307 marc_field => '952l',
316 marc_type => 'marc21',
317 marc_field => '260c',
320 name => 'date-of-publication',
326 marc_type => 'marc21',
327 marc_field => '008_/7-10',
336 marc_type => 'marc21',
337 marc_field => '650(avxyz)',
341 my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
342 $se->mock('_foreach_mapping', sub {
343 my ($self, $sub) = @_;
345 foreach my $map (@mappings) {
359 my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
361 my $callno = 'ABC123';
362 my $callno2 = 'ABC456';
363 my $long_callno = '1234567890' x 30;
365 my $marc_record_1 = MARC::Record->new();
366 $marc_record_1->leader(' cam 22 a 4500');
367 $marc_record_1->append_fields(
368 MARC::Field->new('001', '123'),
369 MARC::Field->new('007', 'ku'),
370 MARC::Field->new('008', '901111s1962 xxk|||| |00| ||eng c'),
371 MARC::Field->new('020', '', '', a => '1-56619-909-3'),
372 MARC::Field->new('100', '', '', a => 'Author 1'),
373 MARC::Field->new('110', '', '', a => 'Corp Author'),
374 MARC::Field->new('210', '', '', a => 'Title 1'),
375 MARC::Field->new('240', '', '4', a => 'The uniform title with nonfiling indicator'),
376 MARC::Field->new('245', '', '', a => 'Title:', b => 'first record'),
377 MARC::Field->new('260', '', '', a => 'New York :', b => 'Ace ,', c => 'c1962'),
378 MARC::Field->new('650', '', '', a => 'Heading', z => 'Geohead', v => 'Formhead'),
379 MARC::Field->new('650', '', '', a => 'Heading', x => 'Gensubhead', z => 'Geohead'),
380 MARC::Field->new('999', '', '', c => '1234567'),
381 # ' ' for testing trimming of white space in boolean value callback:
382 MARC::Field->new('952', '', '', 0 => ' ', g => '123.30', o => $callno, l => 3),
383 MARC::Field->new('952', '', '', 0 => 0, g => '127.20', o => $callno2, l => 2),
384 MARC::Field->new('952', '', '', 0 => 1, g => '0.00', o => $long_callno, l => 1),
386 my $marc_record_2 = MARC::Record->new();
387 $marc_record_2->leader(' cam 22 a 4500');
388 $marc_record_2->append_fields(
389 MARC::Field->new('008', '901111s19uu xxk|||| |00| ||eng c'),
390 MARC::Field->new('100', '', '', a => 'Author 2'),
391 # MARC::Field->new('210', '', '', a => 'Title 2'),
392 # MARC::Field->new('245', '', '', a => 'Title: second record'),
393 MARC::Field->new('260', '', '', a => 'New York :', b => 'Ace ,', c => '1963-2003'),
394 MARC::Field->new('999', '', '', c => '1234568'),
395 MARC::Field->new('952', '', '', 0 => 1, g => 'string where should be numeric', o => $long_callno),
398 my $marc_record_3 = MARC::Record->new();
399 $marc_record_3->leader(' cam 22 a 4500');
400 $marc_record_3->append_fields(
401 MARC::Field->new('008', '901111s19uu xxk|||| |00| ||eng c'),
402 MARC::Field->new('100', '', '', a => 'Author 2'),
403 # MARC::Field->new('210', '', '', a => 'Title 3'),
404 # MARC::Field->new('245', '', '', a => 'Title: third record'),
405 MARC::Field->new('260', '', '', a => 'New York :', b => 'Ace ,', c => ' 89 '),
406 MARC::Field->new('999', '', '', c => '1234568'),
407 MARC::Field->new('952', '', '', 0 => 1, g => 'string where should be numeric', o => $long_callno),
409 my $records = [$marc_record_1, $marc_record_2, $marc_record_3];
411 $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
413 my $docs = $see->marc_records_to_documents($records);
416 is(scalar @{$docs}, 3, 'Two records converted to documents');
418 is_deeply($docs->[0]->{control_number}, ['123'], 'First record control number should be set correctly');
420 is_deeply($docs->[0]->{'ff7-00'}, ['k'], 'First record ff7-00 should be set correctly');
422 is(scalar @{$docs->[0]->{author}}, 2, 'First document author field should contain two values');
423 is_deeply($docs->[0]->{author}, ['Author 1', 'Corp Author'], 'First document author field should be set correctly');
425 is(scalar @{$docs->[0]->{subject}}, 2, 'First document subject field should contain two values');
426 is_deeply($docs->[0]->{subject}, ['Heading Geohead Formhead', 'Heading Gensubhead Geohead'], 'First document asubject field should be set correctly, record order preserved for grouped subfield mapping');
428 is(scalar @{$docs->[0]->{author__sort}}, 1, 'First document author__sort field should have a single value');
429 is_deeply($docs->[0]->{author__sort}, ['Author 1 Corp Author'], 'First document author__sort field should be set correctly');
431 is(scalar @{$docs->[0]->{title__sort}}, 1, 'First document title__sort field should have a single');
432 is_deeply($docs->[0]->{title__sort}, ['Title: first record Title: first record'], 'First document title__sort field should be set correctly');
434 is($docs->[0]->{issues}, 6, 'Issues field should be sum of the issues for each item');
435 is($docs->[0]->{issues__sort}, 6, 'Issues sort field should also be a sum of the issues');
437 is(scalar @{$docs->[0]->{title_wildcard}}, 2, 'First document title_wildcard field should have two values');
438 is_deeply($docs->[0]->{title_wildcard}, ['Title:', 'first record'], 'First document title_wildcard field should be set correctly');
441 is(scalar @{$docs->[0]->{author__suggestion}}, 2, 'First document author__suggestion field should contain two values');
443 $docs->[0]->{author__suggestion},
446 'input' => 'Author 1'
449 'input' => 'Corp Author'
452 'First document author__suggestion field should be set correctly'
455 is(scalar @{$docs->[0]->{title__suggestion}}, 3, 'First document title__suggestion field should contain three values');
457 $docs->[0]->{title__suggestion},
459 { 'input' => 'Title:' },
460 { 'input' => 'first record' },
461 { 'input' => 'Title: first record' }
463 'First document title__suggestion field should be set correctly'
466 ok(!(defined $docs->[0]->{title__facet}), 'First document should have no title__facet field');
468 is(scalar @{$docs->[0]->{author__facet}}, 2, 'First document author__facet field should have two values');
470 $docs->[0]->{author__facet},
471 ['Author 1', 'Corp Author'],
472 'First document author__facet field should be set correctly'
475 is(scalar @{$docs->[0]->{items_withdrawn_status}}, 2, 'First document items_withdrawn_status field should have two values');
477 $docs->[0]->{items_withdrawn_status},
479 'First document items_withdrawn_status field should be set correctly'
483 $docs->[0]->{sum_item_price},
485 'First document sum_item_price field should be set correctly'
488 ok(defined $docs->[0]->{marc_data}, 'First document marc_data field should be set');
489 ok(defined $docs->[0]->{marc_format}, 'First document marc_format field should be set');
490 is($docs->[0]->{marc_format}, 'base64ISO2709', 'First document marc_format should be set correctly');
492 my $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
494 ok($decoded_marc_record->isa('MARC::Record'), "base64ISO2709 record successfully decoded from result");
495 is($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded base64ISO2709 record has same data as original record");
497 is(scalar @{$docs->[0]->{type_of_record}}, 1, 'First document type_of_record field should have one value');
499 $docs->[0]->{type_of_record},
501 'First document type_of_record field should be set correctly'
504 is(scalar @{$docs->[0]->{type_of_record_and_bib_level}}, 1, 'First document type_of_record_and_bib_level field should have one value');
506 $docs->[0]->{type_of_record_and_bib_level},
508 'First document type_of_record_and_bib_level field should be set correctly'
511 is(scalar @{$docs->[0]->{isbn}}, 4, 'First document isbn field should contain four values');
512 is_deeply($docs->[0]->{isbn}, ['978-1-56619-909-4', '9781566199094', '1-56619-909-3', '1566199093'], 'First document isbn field should be set correctly');
515 $docs->[0]->{'local_classification'},
516 [$callno, $callno2, $long_callno],
517 'First document local_classification field should be set correctly'
520 # Nonfiling characters for sort fields
522 $docs->[0]->{uniform_title},
523 ['The uniform title with nonfiling indicator'],
524 'First document uniform_title field should contain the title verbatim'
527 $docs->[0]->{uniform_title__sort},
528 ['uniform title with nonfiling indicator'],
529 'First document uniform_title__sort field should contain the title with the first four initial characters removed'
532 # Tests for 'year' type
533 is(scalar @{$docs->[0]->{'date-of-publication'}}, 1, 'First document date-of-publication field should contain one value');
534 is_deeply($docs->[0]->{'date-of-publication'}, ['1962'], 'First document date-of-publication field should be set correctly');
537 $docs->[0]->{'copydate'},
539 'First document copydate field should be set correctly'
544 is(scalar @{$docs->[1]->{author}}, 1, 'Second document author field should contain one value');
545 is_deeply($docs->[1]->{author}, ['Author 2'], 'Second document author field should be set correctly');
547 is(scalar @{$docs->[1]->{items_withdrawn_status}}, 1, 'Second document items_withdrawn_status field should have one value');
549 $docs->[1]->{items_withdrawn_status},
551 'Second document items_withdrawn_status field should be set correctly'
555 $docs->[1]->{sum_item_price},
557 'Second document sum_item_price field should be set correctly'
561 $docs->[1]->{local_classification__sort},
562 [substr($long_callno, 0, 255)],
563 'Second document local_classification__sort field should be set correctly'
566 # Tests for 'year' type
568 $docs->[1]->{'copydate'},
570 'Second document copydate field should be set correctly'
573 $docs->[1]->{'date-of-publication'},
575 'Second document date-of-publication field should be set correctly'
581 $docs->[2]->{'copydate'},
583 'Third document copydate field should be set correctly'
586 # Mappings marc_type:
588 ok(!(defined $docs->[0]->{unimarc_title}), "No mapping when marc_type doesn't match marc flavour");
590 # Marc serialization format fallback for records exceeding ISO2709 max record size
592 my $large_marc_record = MARC::Record->new();
593 $large_marc_record->leader(' cam 22 a 4500');
595 $large_marc_record->append_fields(
596 MARC::Field->new('100', '', '', a => 'Author 1'),
597 MARC::Field->new('110', '', '', a => 'Corp Author'),
598 MARC::Field->new('210', '', '', a => 'Title 1'),
599 MARC::Field->new('245', '', '', a => 'Title:', b => 'large record'),
600 MARC::Field->new('999', '', '', c => '1234567'),
603 my $item_field = MARC::Field->new('952', '', '', o => '123456789123456789123456789', p => '123456789', z => 'test');
604 my $items_count = 1638;
605 while(--$items_count) {
606 $large_marc_record->append_fields($item_field);
609 $docs = $see->marc_records_to_documents([$large_marc_record]);
611 is($docs->[0]->{marc_format}, 'MARCXML', 'For record exceeding max record size marc_format should be set correctly');
613 $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
615 ok($decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result");
616 is($decoded_marc_record->as_xml_record(), $large_marc_record->as_xml_record(), "Decoded MARCXML record has same data as original record");
624 marc_type => 'marc21',
625 marc_field => '245((ab)ab',
628 my $exception = try {
629 $see->marc_records_to_documents($records);
635 ok(defined $exception, "Exception has been thrown when processing mapping with unmatched opening parenthesis");
636 ok($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
637 ok($exception->message =~ /Unmatched opening parenthesis/, "Exception has the correct message");
646 marc_type => 'marc21',
647 marc_field => '245(ab))ab',
651 $see->marc_records_to_documents($records);
657 ok(defined $exception, "Exception has been thrown when processing mapping with unmatched closing parenthesis");
658 ok($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
659 ok($exception->message =~ /Unmatched closing parenthesis/, "Exception has the correct message");
662 my $marc_record_with_blank_field = MARC::Record->new();
663 $marc_record_with_blank_field->leader(' cam 22 a 4500');
665 $marc_record_with_blank_field->append_fields(
666 MARC::Field->new('100', '', '', a => ''),
667 MARC::Field->new('210', '', '', a => 'Title 1'),
668 MARC::Field->new('245', '', '', a => 'Title:', b => 'large record'),
669 MARC::Field->new('999', '', '', c => '1234567'),
671 $docs = $see->marc_records_to_documents([$marc_record_with_blank_field]);
672 is_deeply( $docs->[0]->{author},[],'No value placed into field if mapped marc field is blank');
673 is_deeply( $docs->[0]->{author__suggestion},[],'No value placed into suggestion if mapped marc field is blank');
677 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents_array () tests' => sub {
681 t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
682 t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ARRAY');
686 name => 'control_number',
692 marc_type => 'marc21',
697 my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
698 $se->mock('_foreach_mapping', sub {
699 my ($self, $sub) = @_;
701 foreach my $map (@mappings) {
715 my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
717 my $marc_record_1 = MARC::Record->new();
718 $marc_record_1->leader(' cam 22 a 4500');
719 $marc_record_1->append_fields(
720 MARC::Field->new('001', '123'),
721 MARC::Field->new('020', '', '', a => '1-56619-909-3'),
722 MARC::Field->new('100', '', '', a => 'Author 1'),
723 MARC::Field->new('110', '', '', a => 'Corp Author'),
724 MARC::Field->new('210', '', '', a => 'Title 1'),
725 MARC::Field->new('245', '', '', a => 'Title:', b => 'first record'),
726 MARC::Field->new('999', '', '', c => '1234567'),
728 my $marc_record_2 = MARC::Record->new();
729 $marc_record_2->leader(' cam 22 a 4500');
730 $marc_record_2->append_fields(
731 MARC::Field->new('100', '', '', a => 'Author 2'),
732 # MARC::Field->new('210', '', '', a => 'Title 2'),
733 # MARC::Field->new('245', '', '', a => 'Title: second record'),
734 MARC::Field->new('999', '', '', c => '1234568'),
735 MARC::Field->new('952', '', '', 0 => 1, g => 'string where should be numeric'),
737 my $records = [ $marc_record_1, $marc_record_2 ];
739 $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
741 my $docs = $see->marc_records_to_documents($records);
744 is(scalar @{$docs}, 2, 'Two records converted to documents');
746 is_deeply($docs->[0]->{control_number}, ['123'], 'First record control number should be set correctly');
748 is($docs->[0]->{marc_format}, 'ARRAY', 'First document marc_format should be set correctly');
750 my $decoded_marc_record = $see->decode_record_from_result($docs->[0]);
752 ok($decoded_marc_record->isa('MARC::Record'), "ARRAY record successfully decoded from result");
753 is($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded ARRAY record has same data as original record");
756 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () authority tests' => sub {
760 t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
761 t::lib::Mocks::mock_preference('ElasticsearchMARCFormat', 'ISO2709');
763 my $builder = t::lib::TestBuilder->new;
764 my $auth_type = $builder->build_object({ class => 'Koha::Authority::Types', value =>{
765 auth_tag_to_report => '150'
777 marc_type => 'marc21',
778 marc_field => '150(aevxyz)',
787 marc_type => 'marc21',
788 marc_field => '185v',
792 my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
793 $se->mock('_foreach_mapping', sub {
794 my ($self, $sub) = @_;
796 foreach my $map (@mappings) {
810 my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::AUTHORITIES_INDEX });
811 my $marc_record_1 = MARC::Record->new();
812 $marc_record_1->append_fields(
813 MARC::Field->new('001', '123'),
814 MARC::Field->new('007', 'ku'),
815 MARC::Field->new('020', '', '', a => '1-56619-909-3'),
816 MARC::Field->new('150', '', '', a => 'Subject', v => 'Genresubdiv', x => 'Generalsubdiv', z => 'Geosubdiv'),
818 my $marc_record_2 = MARC::Record->new();
819 $marc_record_2->append_fields(
820 MARC::Field->new('150', '', '', a => 'Subject', v => 'Genresubdiv', z => 'Geosubdiv', x => 'Generalsubdiv', e => 'wrongsubdiv' ),
822 my $marc_record_3 = MARC::Record->new();
823 $marc_record_3->append_fields(
824 MARC::Field->new('185', '', '', v => 'Formsubdiv' ),
826 my $records = [ $marc_record_1, $marc_record_2, $marc_record_3 ];
828 $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
830 my $docs = $see->marc_records_to_documents($records);
833 [ "Subject formsubdiv Genresubdiv generalsubdiv Generalsubdiv geographicsubdiv Geosubdiv" ],
834 $docs->[0]->{'match-heading'},
835 "First record match-heading should contain the correctly formatted heading"
838 [ "Subject formsubdiv Genresubdiv geographicsubdiv Geosubdiv generalsubdiv Generalsubdiv" ],
839 $docs->[1]->{'match-heading'},
840 "Second record match-heading should contain the correctly formatted heading without wrong subfield"
843 [ "Subject Genresubdiv Geosubdiv Generalsubdiv wrongsubdiv" ],
844 $docs->[1]->{'match'} ,
845 "Second record heading should contain the subfields with record order retained"
847 ok( !exists $docs->[2]->{'match-heading'}, "No match heading defined for subdivision record");
850 $docs->[2]->{'match'} ,
851 "Third record heading should contain the subfield"
856 subtest 'Koha::SearchEngine::Elasticsearch::marc_records_to_documents with IncludeSeeFromInSearches' => sub {
860 t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
861 t::lib::Mocks::mock_preference('IncludeSeeFromInSearches', '1');
862 my $dbh = C4::Context->dbh;
864 my $builder = t::lib::TestBuilder->new;
865 my $auth_type = $builder->build_object({
866 class => 'Koha::Authority::Types',
868 auth_tag_to_report => '150'
871 my $authority_record = MARC::Record->new();
872 $authority_record->append_fields(
873 MARC::Field->new(150, '', '', a => 'Foo'),
874 MARC::Field->new(450, '', '', a => 'Bar'),
876 $dbh->do( "INSERT INTO auth_header (datecreated,marcxml) values (NOW(),?)", undef, ($authority_record->as_xml_record('MARC21') ) );
877 my $authid = $dbh->last_insert_id( undef, undef, 'auth_header', 'authid' );
887 marc_type => 'marc21',
888 marc_field => '650a',
892 my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
893 $se->mock('_foreach_mapping', sub {
894 my ($self, $sub) = @_;
896 foreach my $map (@mappings) {
910 my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
912 my $marc_record_1 = MARC::Record->new();
913 $marc_record_1->leader(' cam 22 a 4500');
914 $marc_record_1->append_fields(
915 MARC::Field->new('001', '123'),
916 MARC::Field->new('245', '', '', a => 'Title'),
917 MARC::Field->new('650', '', '', a => 'Foo', 9 => $authid),
918 MARC::Field->new('999', '', '', c => '1234567'),
921 # sort_fields will call this and use the actual db values unless we call it first
922 $see->get_elasticsearch_mappings();
924 my $docs = $see->marc_records_to_documents([$marc_record_1]);
926 is_deeply($docs->[0]->{subject}, ['Foo', 'Bar'], 'subject should include "See from"');
927 is_deeply($docs->[0]->{subject__facet}, ['Foo'], 'subject__facet should not include "See from"');
928 is_deeply($docs->[0]->{subject__suggestion}, [{ input => 'Foo' }], 'subject__suggestion should not include "See from"');
929 is_deeply($docs->[0]->{subject__sort}, ['Foo'], 'subject__sort should not include "See from"');
932 subtest 'marc_records_to_documents should set the "available" field' => sub {
935 t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
936 my $dbh = C4::Context->dbh;
938 my $se = Test::MockModule->new('Koha::SearchEngine::Elasticsearch');
939 $se->noop('_foreach_mapping');
941 my $see = Koha::SearchEngine::Elasticsearch::Search->new({ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX });
943 # sort_fields will call this and use the actual db values unless we call it first
944 $see->get_elasticsearch_mappings();
946 my $marc_record_1 = MARC::Record->new();
947 $marc_record_1->leader(' cam 22 a 4500');
948 $marc_record_1->append_fields(
949 MARC::Field->new('245', '', '', a => 'Title'),
951 my ($biblionumber) = C4::Biblio::AddBiblio($marc_record_1, '', { defer_marc_save => 1 });
953 my $docs = $see->marc_records_to_documents([$marc_record_1]);
954 is_deeply($docs->[0]->{available}, \0, 'a biblio without items is not available');
956 my $item = Koha::Item->new({
957 biblionumber => $biblionumber,
960 $docs = $see->marc_records_to_documents([$marc_record_1]);
961 is_deeply($docs->[0]->{available}, \1, 'a biblio with one item that has no particular status is available');
963 $item->notforloan(1)->store();
964 $docs = $see->marc_records_to_documents([$marc_record_1]);
965 is_deeply($docs->[0]->{available}, \1, 'a biblio with one item that is "notforloan" is available');
967 $item->set({ notforloan => 0, onloan => '2022-03-03' })->store();
968 $docs = $see->marc_records_to_documents([$marc_record_1]);
969 is_deeply($docs->[0]->{available}, \0, 'a biblio with one item that is on loan is not available');
971 $item->set({ onloan => undef, withdrawn => 1 })->store();
972 $docs = $see->marc_records_to_documents([$marc_record_1]);
973 is_deeply($docs->[0]->{available}, \1, 'a biblio with one item that is withdrawn is available');
975 $item->set({ withdrawn => 0, itemlost => 1 })->store();
976 $docs = $see->marc_records_to_documents([$marc_record_1]);
977 is_deeply($docs->[0]->{available}, \0, 'a biblio with one item that is lost is not available');
979 $item->set({ itemlost => 0, damaged => 1 })->store();
980 $docs = $see->marc_records_to_documents([$marc_record_1]);
981 is_deeply($docs->[0]->{available}, \1, 'a biblio with one item that is damaged is available');
983 my $item2 = Koha::Item->new({
984 biblionumber => $biblionumber,
986 $docs = $see->marc_records_to_documents([$marc_record_1]);
987 is_deeply($docs->[0]->{available}, \1, 'a biblio with at least one item that has no particular status is available');
990 $schema->storage->txn_rollback;