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 => 30;
24 use C4::Biblio qw( AddBiblio ModBiblio ModBiblioMarc );
25 use C4::Circulation qw( AddIssue AddReturn );
28 use Koha::Cache::Memory::Lite;
30 use Koha::Acquisition::Orders;
31 use Koha::AuthorisedValueCategories;
32 use Koha::AuthorisedValues;
33 use Koha::MarcSubfieldStructures;
39 use t::lib::TestBuilder;
44 use_ok('Koha::Biblio');
45 use_ok('Koha::Biblios');
48 my $schema = Koha::Database->new->schema;
49 my $builder = t::lib::TestBuilder->new;
51 subtest 'metadata() tests' => sub {
55 $schema->storage->txn_begin;
57 my $title = 'Oranges and Peaches';
59 my $record = MARC::Record->new();
60 my $field = MARC::Field->new('245','','','a' => $title);
61 $record->append_fields( $field );
62 my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
64 my $biblio = Koha::Biblios->find( $biblionumber );
65 is( ref $biblio, 'Koha::Biblio', 'Found a Koha::Biblio object' );
67 my $metadata = $biblio->metadata;
68 is( ref $metadata, 'Koha::Biblio::Metadata', 'Method metadata() returned a Koha::Biblio::Metadata object' );
70 my $record2 = $metadata->record;
71 is( ref $record2, 'MARC::Record', 'Method record() returned a MARC::Record object' );
73 is( $record2->field('245')->subfield("a"), $title, 'Title in 245$a matches title from original record object' );
75 $schema->storage->txn_rollback;
78 subtest 'hidden_in_opac() tests' => sub {
82 $schema->storage->txn_begin;
84 my $biblio = $builder->build_sample_biblio();
85 my $rules = { withdrawn => [ 2 ] };
87 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
90 !$biblio->hidden_in_opac({ rules => $rules }),
91 'Biblio not hidden if there is no item attached (!OpacHiddenItemsHidesRecord)'
94 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 1 );
97 !$biblio->hidden_in_opac({ rules => $rules }),
98 'Biblio not hidden if there is no item attached (OpacHiddenItemsHidesRecord)'
101 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
102 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
104 $item_1->withdrawn( 1 )->store->discard_changes;
105 $item_2->withdrawn( 1 )->store->discard_changes;
107 ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
109 $item_2->withdrawn( 2 )->store->discard_changes;
110 $biblio->discard_changes; # refresh
112 ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
114 $item_1->withdrawn( 2 )->store->discard_changes;
115 $biblio->discard_changes; # refresh
117 ok( $biblio->hidden_in_opac({ rules => $rules }), 'Biblio hidden' );
119 t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
121 !$biblio->hidden_in_opac( { rules => $rules } ),
122 'Biblio hidden (!OpacHiddenItemsHidesRecord)'
126 $schema->storage->txn_rollback;
129 subtest 'items() tests' => sub {
133 $schema->storage->txn_begin;
135 my $biblio = $builder->build_sample_biblio();
137 is( $biblio->items->count, 0, 'No items, count is 0' );
139 my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
140 my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
142 my $items = $biblio->items;
143 is( ref($items), 'Koha::Items', 'Returns a Koha::Items resultset' );
144 is( $items->count, 2, 'Two items in resultset' );
146 $schema->storage->txn_rollback;
150 subtest 'get_coins and get_openurl' => sub {
154 $schema->storage->txn_begin;
156 my $builder = t::lib::TestBuilder->new;
157 my $biblio = $builder->build_sample_biblio({
163 'ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
164 'GetCOinsBiblio returned right metadata'
167 my $record = MARC::Record->new();
168 $record->append_fields( MARC::Field->new('100','','','a' => 'Author 2'), MARC::Field->new('880','','','a' => 'Something') );
169 my ( $biblionumber ) = C4::Biblio::AddBiblio($record, '');
170 my $biblio_no_title = Koha::Biblios->find($biblionumber);
172 $biblio_no_title->get_coins,
173 'ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.au=Author%202',
174 'GetCOinsBiblio returned right metadata if biblio does not have a title'
177 t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/");
179 $biblio->get_openurl,
180 'https://koha.example.com/?ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
181 'Koha::Biblio->get_openurl returned right URL'
184 t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/?client_id=ci1");
186 $biblio->get_openurl,
187 'https://koha.example.com/?client_id=ci1&ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=book&rft.btitle=Title%201&rft.au=Author%201',
188 'Koha::Biblio->get_openurl returned right URL'
191 $schema->storage->txn_rollback;
194 subtest 'is_serial() tests' => sub {
198 $schema->storage->txn_begin;
200 my $biblio = $builder->build_sample_biblio();
202 $biblio->serial( 1 )->store->discard_changes;
203 ok( $biblio->is_serial, 'Bibliographic record is serial' );
205 $biblio->serial( 0 )->store->discard_changes;
206 ok( !$biblio->is_serial, 'Bibliographic record is not serial' );
208 my $record = $biblio->metadata->record;
209 $record->leader('00142nas a22 7a 4500');
210 ModBiblio($record, $biblio->biblionumber );
211 $biblio = Koha::Biblios->find($biblio->biblionumber);
213 ok( $biblio->is_serial, 'Bibliographic record is serial' );
215 $schema->storage->txn_rollback;
218 subtest 'pickup_locations() tests' => sub {
222 $schema->storage->txn_begin;
224 Koha::CirculationRules->search->delete;
225 Koha::CirculationRules->set_rules(
227 categorycode => undef,
231 reservesallowed => 25,
236 my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
237 my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
238 my $root3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
240 my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'zzz' } } );
241 my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'AAA' } } );
242 my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'FFF' } } );
243 my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'CCC' } } );
244 my $library5 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'eee' } } );
245 my $library6 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'BBB' } } );
246 my $library7 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'DDD' } } );
247 my $library8 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'GGG' } } );
249 our @branchcodes = map { $_->branchcode } ($library1, $library2, $library3, $library4, $library5, $library6, $library7, $library8);
251 Koha::CirculationRules->set_rules(
253 branchcode => $library1->branchcode,
256 holdallowed => 'from_home_library',
257 hold_fulfillment_policy => 'any',
258 returnbranch => 'any'
263 Koha::CirculationRules->set_rules(
265 branchcode => $library2->branchcode,
268 holdallowed => 'from_local_hold_group',
269 hold_fulfillment_policy => 'holdgroup',
270 returnbranch => 'any'
275 Koha::CirculationRules->set_rules(
277 branchcode => $library3->branchcode,
280 holdallowed => 'from_local_hold_group',
281 hold_fulfillment_policy => 'patrongroup',
282 returnbranch => 'any'
287 Koha::CirculationRules->set_rules(
289 branchcode => $library4->branchcode,
292 holdallowed => 'from_any_library',
293 hold_fulfillment_policy => 'holdingbranch',
294 returnbranch => 'any'
299 Koha::CirculationRules->set_rules(
301 branchcode => $library5->branchcode,
304 holdallowed => 'from_any_library',
305 hold_fulfillment_policy => 'homebranch',
306 returnbranch => 'any'
311 Koha::CirculationRules->set_rules(
313 branchcode => $library6->branchcode,
316 holdallowed => 'from_home_library',
317 hold_fulfillment_policy => 'holdgroup',
318 returnbranch => 'any'
323 Koha::CirculationRules->set_rules(
325 branchcode => $library7->branchcode,
328 holdallowed => 'from_local_hold_group',
329 hold_fulfillment_policy => 'holdingbranch',
330 returnbranch => 'any'
336 Koha::CirculationRules->set_rules(
338 branchcode => $library8->branchcode,
341 holdallowed => 'from_any_library',
342 hold_fulfillment_policy => 'patrongroup',
343 returnbranch => 'any'
348 my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
349 my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
351 my $group2_3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
352 my $group2_4 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
354 my $group3_5 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library5->branchcode } } );
355 my $group3_6 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library6->branchcode } } );
356 my $group3_7 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library7->branchcode } } );
357 my $group3_8 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library8->branchcode } } );
359 my $biblio1 = $builder->build_sample_biblio({ title => '1' });
360 my $biblio2 = $builder->build_sample_biblio({ title => '2' });
363 { $biblio1->pickup_locations }
364 'Koha::Exceptions::MissingParameter',
365 'Exception thrown on missing parameter';
367 is( $@->parameter, 'patron', 'Exception param correctly set' );
369 my $item1_1 = $builder->build_sample_item({
370 biblionumber => $biblio1->biblionumber,
371 homebranch => $library1->branchcode,
372 holdingbranch => $library2->branchcode,
375 my $item1_3 = $builder->build_sample_item({
376 biblionumber => $biblio1->biblionumber,
377 homebranch => $library3->branchcode,
378 holdingbranch => $library4->branchcode,
381 my $item1_7 = $builder->build_sample_item({
382 biblionumber => $biblio1->biblionumber,
383 homebranch => $library7->branchcode,
384 holdingbranch => $library4->branchcode,
387 my $item2_2 = $builder->build_sample_item({
388 biblionumber => $biblio2->biblionumber,
389 homebranch => $library2->branchcode,
390 holdingbranch => $library1->branchcode,
393 my $item2_4 = $builder->build_sample_item({
394 biblionumber => $biblio2->biblionumber,
395 homebranch => $library4->branchcode,
396 holdingbranch => $library3->branchcode,
399 my $item2_6 = $builder->build_sample_item({
400 biblionumber => $biblio2->biblionumber,
401 homebranch => $library6->branchcode,
402 holdingbranch => $library4->branchcode,
405 my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'1', branchcode => $library1->branchcode } } );
406 my $patron8 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'8', branchcode => $library8->branchcode } } );
409 "ItemHomeLibrary-1-1" => 6,
410 "ItemHomeLibrary-1-8" => 1,
411 "ItemHomeLibrary-2-1" => 2,
412 "ItemHomeLibrary-2-8" => 0,
413 "PatronLibrary-1-1" => 6,
414 "PatronLibrary-1-8" => 3,
415 "PatronLibrary-2-1" => 0,
416 "PatronLibrary-2-8" => 3,
420 my ( $cbranch, $biblio, $patron, $results ) = @_;
421 t::lib::Mocks::mock_preference('ReservesControlBranch', $cbranch);
424 my $pickup_location = $_;
425 grep { $pickup_location->branchcode eq $_ } @branchcodes
426 } $biblio->pickup_locations( { patron => $patron } )->as_list;
429 scalar(@pl) == $results->{ $cbranch . '-'
430 . $biblio->title . '-'
431 . $patron->firstname },
432 'ReservesControlBranch: '
439 . $results->{ $cbranch . '-'
440 . $biblio->title . '-'
441 . $patron->firstname }
447 foreach my $cbranch ('ItemHomeLibrary','PatronLibrary') {
448 my $cache = Koha::Cache::Memory::Lite->get_instance();
449 $cache->flush(); # needed since we change ReservesControlBranch
450 foreach my $biblio ($biblio1, $biblio2) {
451 foreach my $patron ($patron1, $patron8) {
452 _doTest($cbranch, $biblio, $patron, $results);
457 my @pl_names = map { $_->branchname } $biblio1->pickup_locations( { patron => $patron1 } )->as_list;
458 my $pl_ori_str = join('|', @pl_names);
459 my $pl_sorted_str = join('|', sort { lc($a) cmp lc($b) } @pl_names);
461 $pl_ori_str eq $pl_sorted_str,
462 'Libraries must be sorted by name'
464 $schema->storage->txn_rollback;
467 subtest 'to_api() tests' => sub {
469 $schema->storage->txn_begin;
471 my $biblio = $builder->build_sample_biblio();
472 my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
474 my $biblioitem_api = $biblio->biblioitem->to_api;
475 my $biblio_api = $biblio->to_api;
477 plan tests => (scalar keys %{ $biblioitem_api }) + 1;
479 foreach my $key ( keys %{ $biblioitem_api } ) {
480 is( $biblio_api->{$key}, $biblioitem_api->{$key}, "$key is added to the biblio object" );
483 $biblio_api = $biblio->to_api({ embed => { items => {} } });
484 is_deeply( $biblio_api->{items}, [ $item->to_api ], 'Item correctly embedded' );
486 $schema->storage->txn_rollback;
489 subtest 'suggestions() tests' => sub {
493 $schema->storage->txn_begin;
495 my $biblio = $builder->build_sample_biblio();
497 is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
500 $biblio->suggestions->unblessed,
502 '->suggestions returns an empty Koha::Suggestions resultset'
505 my $suggestion = $builder->build_object(
507 class => 'Koha::Suggestions',
508 value => { biblionumber => $biblio->biblionumber }
512 my $suggestions = $biblio->suggestions->unblessed;
515 $biblio->suggestions->unblessed,
516 [ $suggestion->unblessed ],
517 '->suggestions returns the related Koha::Suggestion objects'
520 $schema->storage->txn_rollback;
523 subtest 'get_marc_components() tests' => sub {
527 $schema->storage->txn_begin;
529 my ($host_bibnum) = C4::Biblio::AddBiblio(host_record(), '');
530 my $host_biblio = Koha::Biblios->find($host_bibnum);
531 t::lib::Mocks::mock_preference( 'SearchEngine', 'Zebra' );
532 my $search_mod = Test::MockModule->new( 'Koha::SearchEngine::Zebra::Search' );
533 $search_mod->mock( 'search_compat', \&search_component_record2 );
535 my $components = $host_biblio->get_marc_components;
536 is( ref($components), 'ARRAY', 'Return type is correct' );
541 '->get_marc_components returns an empty ARRAY'
544 $search_mod->unmock( 'search_compat');
545 $search_mod->mock( 'search_compat', \&search_component_record1 );
546 my $component_record = component_record1()->as_xml();
549 $host_biblio->get_marc_components,
551 '->get_marc_components returns the related component part record'
553 $search_mod->unmock( 'search_compat');
555 $search_mod->mock( 'search_compat',
556 sub { Koha::Exception->throw("error searching analytics") }
558 warning_like { $components = $host_biblio->get_marc_components }
559 qr{Warning from search_compat: .* 'error searching analytics'};
562 $host_biblio->object_messages,
566 message => 'component_search',
567 payload => "Exception 'Koha::Exception' thrown 'error searching analytics'\n"
571 $search_mod->unmock( 'search_compat');
573 $schema->storage->txn_rollback;
576 subtest 'get_components_query' => sub {
579 my $biblio = $builder->build_sample_biblio();
580 my $biblionumber = $biblio->biblionumber;
581 my $record = $biblio->metadata->record;
583 foreach my $engine ('Zebra','Elasticsearch'){
584 t::lib::Mocks::mock_preference( 'SearchEngine', $engine );
586 t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
587 t::lib::Mocks::mock_preference( 'ComponentSortField', 'author' );
588 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'za' );
589 my ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
590 is($comp_query_str, 'Host-item:("Some boring read")', "$engine: UseControlNumber disabled");
591 is($comp_sort, "author_za", "$engine: UseControlNumber disabled sort is correct");
593 t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
594 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'az' );
595 my $marc_001_field = MARC::Field->new('001', $biblionumber);
596 $record->append_fields($marc_001_field);
597 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
598 $biblio = Koha::Biblios->find( $biblio->biblionumber);
600 ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
601 is($comp_query_str, "(rcn:\"$biblionumber\" AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled without MarcOrgCode");
602 is($comp_sort, "author_az", "$engine: UseControlNumber enabled without MarcOrgCode sort is correct");
604 my $marc_003_field = MARC::Field->new('003', 'OSt');
605 $record->append_fields($marc_003_field);
606 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
607 $biblio = Koha::Biblios->find( $biblio->biblionumber);
609 t::lib::Mocks::mock_preference( 'ComponentSortField', 'title' );
610 t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'asc' );
611 ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
612 is($comp_query_str, "(((rcn:\"$biblionumber\" AND cni:\"OSt\") OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled with MarcOrgCode");
613 is($comp_sort, "title_asc", "$engine: UseControlNumber enabled with MarcOrgCode sort if correct");
614 $record->delete_field($marc_003_field);
618 subtest 'get_volumes_query' => sub {
621 my $biblio = $builder->build_sample_biblio();
622 my $biblionumber = $biblio->biblionumber;
623 my $record = $biblio->metadata->record;
625 # Ensure our mocked record is captured as a set or monographic series
626 my $ldr = $record->leader();
627 substr( $ldr, 19, 1 ) = 'a';
628 $record->leader($ldr);
629 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
630 $biblio = Koha::Biblios->find( $biblio->biblionumber );
632 t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
634 $biblio->get_volumes_query,
635 "(title-series,phr:(\"Some boring read\") OR Host-item,phr:(\"Some boring read\") NOT (bib-level:a OR bib-level:b))",
636 "UseControlNumber disabled"
639 t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
640 my $marc_001_field = MARC::Field->new( '001', $biblionumber );
641 $record->append_fields($marc_001_field);
642 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
643 $biblio = Koha::Biblios->find( $biblio->biblionumber );
647 $biblio->get_volumes_query, "(rcn:$biblionumber NOT (bib-level:a OR bib-level:b))",
648 "UseControlNumber enabled without MarcOrgCode"
651 my $marc_003_field = MARC::Field->new( '003', 'OSt' );
652 $record->append_fields($marc_003_field);
653 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
654 $biblio = Koha::Biblios->find( $biblio->biblionumber );
657 $biblio->get_volumes_query,
658 "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") NOT (bib-level:a OR bib-level:b))",
659 "UseControlNumber enabled with MarcOrgCode"
663 subtest 'orders() and active_orders() tests' => sub {
667 $schema->storage->txn_begin;
669 my $biblio = $builder->build_sample_biblio();
671 my $orders = $biblio->orders;
672 my $active_orders = $biblio->active_orders;
674 is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
675 is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
677 # Add a couple orders
679 $builder->build_object(
681 class => 'Koha::Acquisition::Orders',
683 biblionumber => $biblio->biblionumber,
684 datecancellationprinted => '2019-12-31'
690 $builder->build_object(
692 class => 'Koha::Acquisition::Orders',
694 biblionumber => $biblio->biblionumber,
695 datecancellationprinted => undef
700 $orders = $biblio->orders;
701 $active_orders = $biblio->active_orders;
703 is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
704 is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
705 is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
707 $schema->storage->txn_rollback;
710 subtest 'tickets() tests' => sub {
714 $schema->storage->txn_begin;
716 my $biblio = $builder->build_sample_biblio();
717 my $tickets = $biblio->tickets;
718 is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
719 is( $tickets->count, 0, 'Koha::Biblio->tickets should return a count of 0 when there are no related tickets' );
723 $builder->build_object(
725 class => 'Koha::Tickets',
726 value => { biblio_id => $biblio->biblionumber }
731 $tickets = $biblio->tickets;
732 is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
733 is( $tickets->count, 2, 'Koha::Biblio->tickets should return the correct number of tickets' );
735 $schema->storage->txn_rollback;
738 subtest 'subscriptions() tests' => sub {
742 $schema->storage->txn_begin;
744 my $biblio = $builder->build_sample_biblio;
746 my $subscriptions = $biblio->subscriptions;
747 is( ref($subscriptions), 'Koha::Subscriptions',
748 'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
750 is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
752 # Add two subscriptions
754 $builder->build_object(
756 class => 'Koha::Subscriptions',
757 value => { biblionumber => $biblio->biblionumber }
762 $subscriptions = $biblio->subscriptions;
763 is( ref($subscriptions), 'Koha::Subscriptions',
764 'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
766 is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
768 $schema->storage->txn_rollback;
771 subtest 'get_marc_notes() MARC21 tests' => sub {
774 $schema->storage->txn_begin;
776 t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
778 my $av = $builder->build_object( { class => 'Koha::AuthorisedValues' } );
780 my $biblio = $builder->build_sample_biblio;
781 my $record = $biblio->metadata->record;
782 $record->append_fields(
783 MARC::Field->new( '500', '', '', a => 'Note1' ),
784 MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
785 MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
786 MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
787 MARC::Field->new( '544', '', '', a => 'Note5' ),
788 MARC::Field->new( '590', '', '', a => $av->authorised_value ),
789 MARC::Field->new( '545', '', '', a => 'Invisible on OPAC' ),
792 my $mss = Koha::MarcSubfieldStructures->find({tagfield => "590", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
793 $mss->update({ authorised_value => $av->category });
795 $mss = Koha::MarcSubfieldStructures->find({tagfield => "545", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
796 $mss->update({ hidden => 1 });
798 my $cache = Koha::Caches->get_instance;
799 $cache->clear_from_cache("MarcStructure-0-");
800 $cache->clear_from_cache("MarcStructure-1-");
801 $cache->clear_from_cache("MarcSubfieldStructure-");
802 $cache->clear_from_cache("MarcCodedFields-");
804 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
805 $biblio = Koha::Biblios->find( $biblio->biblionumber);
807 my $notes = $biblio->get_marc_notes;
808 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
809 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
810 is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
811 is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Note shows if not opac (Hidden by Indicator)" );
812 is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
813 is( $notes->[5]->{marcnote}, $av->lib, 'Authorised value is correctly parsed to show description rather than code' );
814 is( $notes->[6]->{marcnote}, 'Invisible on OPAC', 'Note shows if not opac (Hidden by framework)' );
815 is( @$notes, 7, 'No more notes' );
816 $notes = $biblio->get_marc_notes({ opac => 1 });
817 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
818 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
819 is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
820 is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
821 is( $notes->[4]->{marcnote}, $av->lib_opac, 'Authorised value is correctly parsed for OPAC to show description rather than code' );
822 is( @$notes, 5, 'No more notes' );
824 $cache->clear_from_cache("MarcStructure-0-");
825 $cache->clear_from_cache("MarcStructure-1-");
826 $cache->clear_from_cache("MarcSubfieldStructure-");
827 $cache->clear_from_cache("MarcCodedFields-");
829 $schema->storage->txn_rollback;
832 subtest 'get_marc_notes() UNIMARC tests' => sub {
835 $schema->storage->txn_begin;
837 t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
838 t::lib::Mocks::mock_preference( 'marcflavour', 'UNIMARC' );
840 my $biblio = $builder->build_sample_biblio;
841 my $record = $biblio->metadata->record;
842 $record->append_fields(
843 MARC::Field->new( '300', '', '', a => 'Note1' ),
844 MARC::Field->new( '300', '', '', a => 'Note2' ),
845 MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
847 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
848 $biblio = Koha::Biblios->find( $biblio->biblionumber);
849 my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
850 is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
851 is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
852 is( @$notes, 2, 'No more notes' );
854 t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
855 $schema->storage->txn_rollback;
858 subtest 'host_items() tests' => sub {
861 $schema->storage->txn_begin;
863 my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
865 t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
866 my $host_items = $biblio->host_items;
867 is( ref($host_items), 'Koha::Items' );
868 is( $host_items->count, 0 );
871 $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
872 my $host_item_1 = $builder->build_sample_item;
873 my $host_item_2 = $builder->build_sample_item;
875 my $record = $biblio->metadata->record;
876 $record->append_fields(
879 9 => $host_item_1->itemnumber,
880 9 => $host_item_2->itemnumber
883 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
884 $biblio = $biblio->get_from_storage;
885 $host_items = $biblio->host_items;
886 is( $host_items->count, 2 );
887 is_deeply( [ $host_items->get_column('itemnumber') ],
888 [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
890 t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
891 $host_items = $biblio->host_items;
892 is( ref($host_items), 'Koha::Items' );
893 is( $host_items->count, 0 );
895 subtest 'test host_items param in items()' => sub {
898 my $items = $biblio->items;
899 is( $items->count, 1, "Without host_items param we only get the items on the biblio");
900 $items = $biblio->items({ host_items => 1 });
901 is( $items->count, 3, "With param host_items we get the biblio items plus analytics");
902 is( ref($items), 'Koha::Items', "We correctly get an Items object");
903 is_deeply( [ $items->get_column('itemnumber') ],
904 [ $item_1->itemnumber, $host_item_1->itemnumber, $host_item_2->itemnumber ] );
907 $schema->storage->txn_rollback;
910 subtest 'article_requests() tests' => sub {
914 $schema->storage->txn_begin;
916 my $item = $builder->build_sample_item;
917 my $biblio = $item->biblio;
919 my $article_requests = $biblio->article_requests;
920 is( ref($article_requests), 'Koha::ArticleRequests',
921 'In scalar context, type is correct' );
922 is( $article_requests->count, 0, 'No article requests' );
924 foreach my $i ( 0 .. 3 ) {
926 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
928 Koha::ArticleRequest->new(
930 borrowernumber => $patron->id,
931 biblionumber => $biblio->id,
932 itemnumber => $item->id,
933 title => $biblio->title,
938 $article_requests = $biblio->article_requests;
939 is( $article_requests->count, 4, '4 article requests' );
941 $schema->storage->txn_rollback;
944 subtest 'current_checkouts() and old_checkouts() tests' => sub {
948 $schema->storage->txn_begin;
950 my $library = $builder->build_object({ class => 'Koha::Libraries' });
952 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
953 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
955 my $item_1 = $builder->build_sample_item;
956 my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
958 t::lib::Mocks::mock_userenv({ branchcode => $library->id });
960 AddIssue( $patron_1, $item_1->barcode );
961 AddIssue( $patron_1, $item_2->barcode );
963 AddReturn( $item_1->barcode );
964 AddIssue( $patron_2, $item_1->barcode );
966 my $biblio = $item_1->biblio;
967 my $current_checkouts = $biblio->current_checkouts;
968 my $old_checkouts = $biblio->old_checkouts;
970 is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
971 is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
973 is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
974 is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
976 $schema->storage->txn_rollback;
979 subtest 'get_marc_contributors() tests' => sub {
983 $schema->storage->txn_begin;
985 my $biblio = $builder->build_sample_biblio({ author => 'Main author' });
986 my $record = $biblio->metadata->record;
988 # add author information
989 my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
990 $record->append_fields($field);
991 $field = MARC::Field->new('701','1','','d' => 'Secondary author 2');
992 $record->append_fields($field);
995 C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
996 $biblio = Koha::Biblios->find( $biblio->biblionumber );
998 is( @{$biblio->get_marc_authors}, 3, 'get_marc_authors retrieves correct number of author subfields' );
999 is( @{$biblio->get_marc_contributors}, 2, 'get_marc_contributors retrieves correct number of author subfields' );
1000 $schema->storage->txn_rollback;
1003 subtest 'Recalls tests' => sub {
1007 $schema->storage->txn_begin;
1009 my $item1 = $builder->build_sample_item;
1010 my $biblio = $item1->biblio;
1011 my $branchcode = $item1->holdingbranch;
1012 my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1013 my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1014 my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1015 my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
1016 t::lib::Mocks::mock_userenv({ patron => $patron1 });
1018 my $recall1 = Koha::Recall->new(
1019 { patron_id => $patron1->borrowernumber,
1020 created_date => \'NOW()',
1021 biblio_id => $biblio->biblionumber,
1022 pickup_library_id => $branchcode,
1023 item_id => $item1->itemnumber,
1024 expiration_date => undef,
1028 my $recall2 = Koha::Recall->new(
1029 { patron_id => $patron2->borrowernumber,
1030 created_date => \'NOW()',
1031 biblio_id => $biblio->biblionumber,
1032 pickup_library_id => $branchcode,
1034 expiration_date => undef,
1038 my $recall3 = Koha::Recall->new(
1039 { patron_id => $patron3->borrowernumber,
1040 created_date => \'NOW()',
1041 biblio_id => $biblio->biblionumber,
1042 pickup_library_id => $branchcode,
1043 item_id => $item1->itemnumber,
1044 expiration_date => undef,
1049 my $recalls = $biblio->recalls;
1050 is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
1052 $recall1->set_cancelled;
1053 $recall2->set_expired({ interface => 'COMMANDLINE' });
1055 is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
1056 is( $recalls->filter_by_current->count, 1, 'Correctly get number of active recalls for biblio' );
1058 t::lib::Mocks::mock_preference('UseRecalls', 0);
1059 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
1061 t::lib::Mocks::mock_preference("UseRecalls", 1);
1062 $item1->update({ notforloan => 1 });
1063 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
1065 $item1->update({ notforloan => 0 });
1066 Koha::CirculationRules->set_rules({
1067 branchcode => $branchcode,
1068 categorycode => $patron1->categorycode,
1069 itemtype => $item1->effective_itemtype,
1071 recalls_allowed => 0,
1072 recalls_per_record => 1,
1073 on_shelf_recalls => 'all',
1076 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
1078 Koha::CirculationRules->set_rules({
1079 branchcode => $branchcode,
1080 categorycode => $patron1->categorycode,
1081 itemtype => $item1->effective_itemtype,
1083 recalls_allowed => 1,
1084 recalls_per_record => 1,
1085 on_shelf_recalls => 'all',
1088 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
1089 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
1091 $recall1->set_cancelled;
1092 C4::Circulation::AddIssue( $patron1, $item2->barcode );
1093 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
1095 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
1097 Koha::CirculationRules->set_rules({
1098 branchcode => $branchcode,
1099 categorycode => $patron1->categorycode,
1100 itemtype => $item1->effective_itemtype,
1102 recalls_allowed => 1,
1103 recalls_per_record => 1,
1104 on_shelf_recalls => 'any',
1107 C4::Circulation::AddReturn( $item2->barcode, $branchcode );
1108 is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
1110 $recall2->set_cancelled;
1111 C4::Circulation::AddIssue( $patron2, $item2->barcode );
1112 C4::Circulation::AddIssue( $patron2, $item1->barcode );
1113 is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
1115 $item1->update({ withdrawn => 1 });
1116 is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
1118 $schema->storage->txn_rollback;
1121 subtest 'ill_requests() tests' => sub {
1125 $schema->storage->txn_begin;
1127 my $biblio = $builder->build_sample_biblio;
1129 my $rs = $biblio->ill_requests;
1130 is( ref($rs), 'Koha::Illrequests' );
1131 is( $rs->count, 0, 'No linked requests' );
1134 $builder->build_object(
1136 class => 'Koha::Illrequests',
1137 value => { biblio_id => $biblio->id }
1142 is( $biblio->ill_requests->count, 10, 'Linked requests are present' );
1144 $schema->storage->txn_rollback;
1147 subtest 'item_groups() tests' => sub {
1151 $schema->storage->txn_begin;
1153 my $biblio = $builder->build_sample_biblio();
1155 my @item_groups = $biblio->item_groups->as_list;
1156 is( scalar(@item_groups), 0, 'Got zero item groups');
1158 my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1160 @item_groups = $biblio->item_groups->as_list;
1161 is( scalar(@item_groups), 1, 'Got one item group');
1162 is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
1164 my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1166 @item_groups = $biblio->item_groups->as_list;
1167 is( scalar(@item_groups), 2, 'Got two item groups');
1168 is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
1169 is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
1171 $schema->storage->txn_rollback;
1174 subtest 'normalized_isbn' => sub {
1177 # We will move the tests from GetNormalizedISBN here when it will get replaced
1178 my $biblio = $builder->build_sample_biblio();
1179 $biblio->biblioitem->set( { isbn => '9781250067128 | 125006712X' } )->store;
1181 $biblio->normalized_isbn, C4::Koha::GetNormalizedISBN( $biblio->biblioitem->isbn ),
1182 'normalized_isbn is a wrapper around C4::Koha::GetNormalizedISBN'
1186 subtest 'normalized_upc' => sub {
1189 # We will move the tests from GetNormalizedUPC here when it will get replaced
1190 # Note that only a single test exist and it's not really meaningful...
1191 my $biblio = $builder->build_sample_biblio();
1193 $biblio->normalized_upc, C4::Koha::GetNormalizedUPC( $biblio->metadata->record ),
1194 'normalized_upc is a wrapper around C4::Koha::GetNormalizedUPC'
1198 subtest 'normalized_oclc' => sub {
1201 # We will move the tests from GetNormalizedOCLC here when it will get replaced
1202 # Note that only a single test exist and it's not really meaningful...
1203 my $biblio = $builder->build_sample_biblio();
1205 $biblio->normalized_oclc, C4::Koha::GetNormalizedOCLCNumber( $biblio->metadata->record ),
1206 'normalized_oclc is a wrapper around C4::Koha::GetNormalizedOCLCNumber'
1210 subtest 'ratings' => sub {
1212 # See t/db_dependent/Koha/Ratings.t
1216 subtest 'opac_summary_html' => sub {
1220 my $author = 'my author';
1221 my $title = 'my title';
1222 my $isbn = '9781250067128 | 125006712X';
1223 my $biblio = $builder->build_sample_biblio( { author => $author, title => $title } );
1224 $biblio->biblioitem->set( { isbn => '9781250067128 | 125006712X' } )->store;
1226 t::lib::Mocks::mock_preference( 'OPACMySummaryHTML', '' );
1227 is( $biblio->opac_summary_html, '', 'opac_summary_html returns empty string if pref is off' );
1229 t::lib::Mocks::mock_preference(
1230 'OPACMySummaryHTML',
1231 'Replace {AUTHOR}, {TITLE}, {ISBN} AND {BIBLIONUMBER} please'
1234 $biblio->opac_summary_html,
1235 sprintf( 'Replace %s, %s, %s AND %s please', $author, $title, $biblio->normalized_isbn, $biblio->biblionumber ),
1236 'opac_summary_html replaces the different patterns'
1240 sub component_record1 {
1241 my $marc = MARC::Record->new;
1242 $marc->append_fields(
1243 MARC::Field->new( '001', '3456' ),
1244 MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1245 MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1249 sub search_component_record1 {
1250 my @results = ( component_record1()->as_xml() );
1251 return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
1254 sub search_component_record2 {
1256 return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
1260 my $marc = MARC::Record->new;
1261 $marc->append_fields(
1262 MARC::Field->new( '001', '1234' ),
1263 MARC::Field->new( '003', 'FIRST' ),
1264 MARC::Field->new( '245', '', '', a => 'Some title' ),