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
=> 5;
30 use Koha
::SearchEngine
::Elasticsearch
;
31 use Koha
::SearchEngine
::Elasticsearch
::Search
;
33 subtest
'_read_configuration() tests' => sub {
38 t
::lib
::Mocks
::mock_config
( 'elasticsearch', undef );
40 # 'elasticsearch' missing in configuration
42 $configuration = Koha
::SearchEngine
::Elasticsearch
::_read_configuration
;
44 'Koha::Exceptions::Config::MissingEntry',
45 'Configuration problem, exception thrown';
48 "Missing 'elasticsearch' block in config file",
49 'Exception message is correct'
52 # 'elasticsearch' present but no 'server' entry
53 t
::lib
::Mocks
::mock_config
( 'elasticsearch', {} );
55 $configuration = Koha
::SearchEngine
::Elasticsearch
::_read_configuration
;
57 'Koha::Exceptions::Config::MissingEntry',
58 'Configuration problem, exception thrown';
61 "Missing 'server' entry in config file for elasticsearch",
62 'Exception message is correct'
65 # 'elasticsearch' and 'server' entries present, but no 'index_name'
66 t
::lib
::Mocks
::mock_config
( 'elasticsearch', { server
=> 'a_server' } );
68 $configuration = Koha
::SearchEngine
::Elasticsearch
::_read_configuration
;
70 'Koha::Exceptions::Config::MissingEntry',
71 'Configuration problem, exception thrown';
74 "Missing 'index_name' entry in config file for elasticsearch",
75 'Exception message is correct'
78 # Correct configuration, only one server
79 t
::lib
::Mocks
::mock_config
( 'elasticsearch', { server
=> 'a_server', index_name
=> 'index' } );
81 $configuration = Koha
::SearchEngine
::Elasticsearch
::_read_configuration
;
82 is
( $configuration->{index_name
}, 'index', 'Index configuration parsed correctly' );
83 is_deeply
( $configuration->{nodes
}, ['a_server'], 'Server configuration parsed correctly' );
85 # Correct configuration, two servers
86 my @servers = ('a_server', 'another_server');
87 t
::lib
::Mocks
::mock_config
( 'elasticsearch', { server
=> \
@servers, 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
}, \
@servers , 'Server configuration parsed correctly' );
94 subtest
'get_elasticsearch_settings() tests' => sub {
100 # test reading index settings
101 my $es = Koha
::SearchEngine
::Elasticsearch
->new( {index => $Koha::SearchEngine
::Elasticsearch
::BIBLIOS_INDEX
} );
102 $settings = $es->get_elasticsearch_settings();
103 is
( $settings->{index}{analysis
}{analyzer
}{analyzer_phrase
}{tokenizer
}, 'keyword', 'Index settings parsed correctly' );
106 subtest
'get_elasticsearch_mappings() tests' => sub {
112 # test reading mappings
113 my $es = Koha
::SearchEngine
::Elasticsearch
->new( {index => $Koha::SearchEngine
::Elasticsearch
::BIBLIOS_INDEX
} );
114 $mappings = $es->get_elasticsearch_mappings();
115 is
( $mappings->{data
}{properties
}{isbn__sort
}{index}, 'false', 'Field mappings parsed correctly' );
118 subtest
'Koha::SearchEngine::Elasticsearch::marc_records_to_documents () tests' => sub {
122 t
::lib
::Mocks
::mock_preference
('marcflavour', 'MARC21');
123 t
::lib
::Mocks
::mock_preference
('ElasticsearchMARCFormat', 'ISO2709');
127 name
=> 'control_number',
133 marc_type
=> 'marc21',
143 marc_type
=> 'marc21',
144 marc_field
=> '020a',
153 marc_type
=> 'marc21',
154 marc_field
=> '100a',
163 marc_type
=> 'marc21',
164 marc_field
=> '110a',
173 marc_type
=> 'marc21',
174 marc_field
=> '245(ab)ab',
177 name
=> 'unimarc_title',
183 marc_type
=> 'unimarc',
184 marc_field
=> '245a',
190 suggestible
=> undef,
193 marc_type
=> 'marc21',
197 name
=> 'title_wildcard',
203 marc_type
=> 'marc21',
207 name
=> 'sum_item_price',
213 marc_type
=> 'marc21',
214 marc_field
=> '952g',
217 name
=> 'items_withdrawn_status',
223 marc_type
=> 'marc21',
224 marc_field
=> '9520',
227 name
=> 'local_classification',
233 marc_type
=> 'marc21',
234 marc_field
=> '952o',
237 name
=> 'type_of_record',
243 marc_type
=> 'marc21',
244 marc_field
=> 'leader_/6',
247 name
=> 'type_of_record_and_bib_level',
253 marc_type
=> 'marc21',
254 marc_field
=> 'leader_/6-7',
263 marc_type
=> 'marc21',
264 marc_field
=> '007_/0',
268 my $se = Test
::MockModule
->new('Koha::SearchEngine::Elasticsearch');
269 $se->mock('_foreach_mapping', sub {
270 my ($self, $sub) = @_;
272 foreach my $map (@mappings) {
286 my $see = Koha
::SearchEngine
::Elasticsearch
::Search
->new({ index => $Koha::SearchEngine
::Elasticsearch
::BIBLIOS_INDEX
});
288 my $callno = 'ABC123';
289 my $callno2 = 'ABC456';
290 my $long_callno = '1234567890' x
30;
292 my $marc_record_1 = MARC
::Record
->new();
293 $marc_record_1->leader(' cam 22 a 4500');
294 $marc_record_1->append_fields(
295 MARC
::Field
->new('001', '123'),
296 MARC
::Field
->new('007', 'ku'),
297 MARC
::Field
->new('020', '', '', a
=> '1-56619-909-3'),
298 MARC
::Field
->new('100', '', '', a
=> 'Author 1'),
299 MARC
::Field
->new('110', '', '', a
=> 'Corp Author'),
300 MARC
::Field
->new('210', '', '', a
=> 'Title 1'),
301 MARC
::Field
->new('245', '', '', a
=> 'Title:', b
=> 'first record'),
302 MARC
::Field
->new('999', '', '', c
=> '1234567'),
303 # ' ' for testing trimming of white space in boolean value callback:
304 MARC
::Field
->new('952', '', '', 0 => ' ', g
=> '123.30', o
=> $callno),
305 MARC
::Field
->new('952', '', '', 0 => 0, g
=> '127.20', o
=> $callno2),
306 MARC
::Field
->new('952', '', '', 0 => 1, g
=> '0.00', o
=> $long_callno),
308 my $marc_record_2 = MARC
::Record
->new();
309 $marc_record_2->leader(' cam 22 a 4500');
310 $marc_record_2->append_fields(
311 MARC
::Field
->new('100', '', '', a
=> 'Author 2'),
312 # MARC::Field->new('210', '', '', a => 'Title 2'),
313 # MARC::Field->new('245', '', '', a => 'Title: second record'),
314 MARC
::Field
->new('999', '', '', c
=> '1234568'),
315 MARC
::Field
->new('952', '', '', 0 => 1, g
=> 'string where should be numeric', o
=> $long_callno),
317 my $records = [$marc_record_1, $marc_record_2];
319 $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
321 my $docs = $see->marc_records_to_documents($records);
324 is
(scalar @
{$docs}, 2, 'Two records converted to documents');
325 is
($docs->[0][0], '1234567', 'First document biblionumber should be set as first element in document touple');
327 is_deeply
($docs->[0][1]->{control_number
}, ['123'], 'First record control number should be set correctly');
329 is_deeply
($docs->[0][1]->{'ff7-00'}, ['k'], 'First record ff7-00 should be set correctly');
331 is
(scalar @
{$docs->[0][1]->{author
}}, 2, 'First document author field should contain two values');
332 is_deeply
($docs->[0][1]->{author
}, ['Author 1', 'Corp Author'], 'First document author field should be set correctly');
334 is
(scalar @
{$docs->[0][1]->{author__sort
}}, 1, 'First document author__sort field should have a single value');
335 is_deeply
($docs->[0][1]->{author__sort
}, ['Author 1 Corp Author'], 'First document author__sort field should be set correctly');
337 is
(scalar @
{$docs->[0][1]->{title__sort
}}, 1, 'First document title__sort field should have a single');
338 is_deeply
($docs->[0][1]->{title__sort
}, ['Title: first record Title: first record'], 'First document title__sort field should be set correctly');
340 is
(scalar @
{$docs->[0][1]->{title_wildcard
}}, 2, 'First document title_wildcard field should have two values');
341 is_deeply
($docs->[0][1]->{title_wildcard
}, ['Title:', 'first record'], 'First document title_wildcard field should be set correctly');
343 is
(scalar @
{$docs->[0][1]->{author__suggestion
}}, 2, 'First document author__suggestion field should contain two values');
345 $docs->[0][1]->{author__suggestion
},
348 'input' => 'Author 1'
351 'input' => 'Corp Author'
354 'First document author__suggestion field should be set correctly'
357 is
(scalar @
{$docs->[0][1]->{title__suggestion
}}, 3, 'First document title__suggestion field should contain three values');
359 $docs->[0][1]->{title__suggestion
},
361 { 'input' => 'Title:' },
362 { 'input' => 'first record' },
363 { 'input' => 'Title: first record' }
365 'First document title__suggestion field should be set correctly'
368 ok
(!(defined $docs->[0][1]->{title__facet
}), 'First document should have no title__facet field');
370 is
(scalar @
{$docs->[0][1]->{author__facet
}}, 2, 'First document author__facet field should have two values');
372 $docs->[0][1]->{author__facet
},
373 ['Author 1', 'Corp Author'],
374 'First document author__facet field should be set correctly'
377 is
(scalar @
{$docs->[0][1]->{items_withdrawn_status
}}, 2, 'First document items_withdrawn_status field should have two values');
379 $docs->[0][1]->{items_withdrawn_status
},
381 'First document items_withdrawn_status field should be set correctly'
385 $docs->[0][1]->{sum_item_price
},
387 'First document sum_item_price field should be set correctly'
390 ok
(defined $docs->[0][1]->{marc_data
}, 'First document marc_data field should be set');
391 ok
(defined $docs->[0][1]->{marc_format
}, 'First document marc_format field should be set');
392 is
($docs->[0][1]->{marc_format
}, 'base64ISO2709', 'First document marc_format should be set correctly');
394 my $decoded_marc_record = $see->decode_record_from_result($docs->[0][1]);
396 ok
($decoded_marc_record->isa('MARC::Record'), "base64ISO2709 record successfully decoded from result");
397 is
($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded base64ISO2709 record has same data as original record");
399 is
(scalar @
{$docs->[0][1]->{type_of_record
}}, 1, 'First document type_of_record field should have one value');
401 $docs->[0][1]->{type_of_record
},
403 'First document type_of_record field should be set correctly'
406 is
(scalar @
{$docs->[0][1]->{type_of_record_and_bib_level
}}, 1, 'First document type_of_record_and_bib_level field should have one value');
408 $docs->[0][1]->{type_of_record_and_bib_level
},
410 'First document type_of_record_and_bib_level field should be set correctly'
413 is
(scalar @
{$docs->[0][1]->{isbn
}}, 4, 'First document isbn field should contain four values');
414 is_deeply
($docs->[0][1]->{isbn
}, ['978-1-56619-909-4', '9781566199094', '1-56619-909-3', '1566199093'], 'First document isbn field should be set correctly');
417 $docs->[0][1]->{'local_classification'},
418 [$callno, $callno2, $long_callno],
419 'First document local_classification field should be set correctly'
424 is
(scalar @
{$docs->[1][1]->{author
}}, 1, 'Second document author field should contain one value');
425 is_deeply
($docs->[1][1]->{author
}, ['Author 2'], 'Second document author field should be set correctly');
427 is
(scalar @
{$docs->[1][1]->{items_withdrawn_status
}}, 1, 'Second document items_withdrawn_status field should have one value');
429 $docs->[1][1]->{items_withdrawn_status
},
431 'Second document items_withdrawn_status field should be set correctly'
435 $docs->[1][1]->{sum_item_price
},
437 'Second document sum_item_price field should be set correctly'
441 $docs->[1][1]->{local_classification__sort
},
442 [substr($long_callno, 0, 255)],
443 'Second document local_classification__sort field should be set correctly'
446 # Mappings marc_type:
448 ok
(!(defined $docs->[0][1]->{unimarc_title
}), "No mapping when marc_type doesn't match marc flavour");
450 # Marc serialization format fallback for records exceeding ISO2709 max record size
452 my $large_marc_record = MARC
::Record
->new();
453 $large_marc_record->leader(' cam 22 a 4500');
455 $large_marc_record->append_fields(
456 MARC
::Field
->new('100', '', '', a
=> 'Author 1'),
457 MARC
::Field
->new('110', '', '', a
=> 'Corp Author'),
458 MARC
::Field
->new('210', '', '', a
=> 'Title 1'),
459 MARC
::Field
->new('245', '', '', a
=> 'Title:', b
=> 'large record'),
460 MARC
::Field
->new('999', '', '', c
=> '1234567'),
463 my $item_field = MARC
::Field
->new('952', '', '', o
=> '123456789123456789123456789', p
=> '123456789', z
=> 'test');
464 my $items_count = 1638;
465 while(--$items_count) {
466 $large_marc_record->append_fields($item_field);
469 $docs = $see->marc_records_to_documents([$large_marc_record]);
471 is
($docs->[0][1]->{marc_format
}, 'MARCXML', 'For record exceeding max record size marc_format should be set correctly');
473 $decoded_marc_record = $see->decode_record_from_result($docs->[0][1]);
475 ok
($decoded_marc_record->isa('MARC::Record'), "MARCXML record successfully decoded from result");
476 is
($decoded_marc_record->as_xml_record(), $large_marc_record->as_xml_record(), "Decoded MARCXML record has same data as original record");
484 marc_type
=> 'marc21',
485 marc_field
=> '245((ab)ab',
488 my $exception = try
{
489 $see->marc_records_to_documents($records);
495 ok
(defined $exception, "Exception has been thrown when processing mapping with unmatched opening parenthesis");
496 ok
($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
497 ok
($exception->message =~ /Unmatched opening parenthesis/, "Exception has the correct message");
506 marc_type
=> 'marc21',
507 marc_field
=> '245(ab))ab',
511 $see->marc_records_to_documents($records);
517 ok
(defined $exception, "Exception has been thrown when processing mapping with unmatched closing parenthesis");
518 ok
($exception->isa("Koha::Exceptions::Elasticsearch::MARCFieldExprParseError"), "Exception is of correct class");
519 ok
($exception->message =~ /Unmatched closing parenthesis/, "Exception has the correct message");
522 subtest
'Koha::SearchEngine::Elasticsearch::marc_records_to_documents_array () tests' => sub {
526 t
::lib
::Mocks
::mock_preference
('marcflavour', 'MARC21');
527 t
::lib
::Mocks
::mock_preference
('ElasticsearchMARCFormat', 'ARRAY');
531 name
=> 'control_number',
537 marc_type
=> 'marc21',
542 my $se = Test
::MockModule
->new('Koha::SearchEngine::Elasticsearch');
543 $se->mock('_foreach_mapping', sub {
544 my ($self, $sub) = @_;
546 foreach my $map (@mappings) {
560 my $see = Koha
::SearchEngine
::Elasticsearch
::Search
->new({ index => $Koha::SearchEngine
::Elasticsearch
::BIBLIOS_INDEX
});
562 my $marc_record_1 = MARC
::Record
->new();
563 $marc_record_1->leader(' cam 22 a 4500');
564 $marc_record_1->append_fields(
565 MARC
::Field
->new('001', '123'),
566 MARC
::Field
->new('020', '', '', a
=> '1-56619-909-3'),
567 MARC
::Field
->new('100', '', '', a
=> 'Author 1'),
568 MARC
::Field
->new('110', '', '', a
=> 'Corp Author'),
569 MARC
::Field
->new('210', '', '', a
=> 'Title 1'),
570 MARC
::Field
->new('245', '', '', a
=> 'Title:', b
=> 'first record'),
571 MARC
::Field
->new('999', '', '', c
=> '1234567'),
573 my $marc_record_2 = MARC
::Record
->new();
574 $marc_record_2->leader(' cam 22 a 4500');
575 $marc_record_2->append_fields(
576 MARC
::Field
->new('100', '', '', a
=> 'Author 2'),
577 # MARC::Field->new('210', '', '', a => 'Title 2'),
578 # MARC::Field->new('245', '', '', a => 'Title: second record'),
579 MARC
::Field
->new('999', '', '', c
=> '1234568'),
580 MARC
::Field
->new('952', '', '', 0 => 1, g
=> 'string where should be numeric'),
582 my $records = [$marc_record_1, $marc_record_2];
584 $see->get_elasticsearch_mappings(); #sort_fields will call this and use the actual db values unless we call it first
586 my $docs = $see->marc_records_to_documents($records);
589 is
(scalar @
{$docs}, 2, 'Two records converted to documents');
591 is
($docs->[0][0], '1234567', 'First document biblionumber should be set as first element in document touple');
593 is_deeply
($docs->[0][1]->{control_number
}, ['123'], 'First record control number should be set correctly');
595 is
($docs->[0][1]->{marc_format
}, 'ARRAY', 'First document marc_format should be set correctly');
597 my $decoded_marc_record = $see->decode_record_from_result($docs->[0][1]);
599 ok
($decoded_marc_record->isa('MARC::Record'), "ARRAY record successfully decoded from result");
600 is
($decoded_marc_record->as_usmarc(), $marc_record_1->as_usmarc(), "Decoded ARRAY record has same data as original record");