Bug 31183: Unit tests
[koha.git] / t / db_dependent / Koha / Biblio.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 => 22; # +1
21 use Test::Warn;
22
23 use C4::Biblio qw( AddBiblio ModBiblio ModBiblioMarc );
24 use C4::Circulation qw( AddIssue AddReturn );
25
26 use Koha::Database;
27 use Koha::Caches;
28 use Koha::Acquisition::Orders;
29 use Koha::AuthorisedValueCategories;
30 use Koha::AuthorisedValues;
31 use Koha::MarcSubfieldStructures;
32 use Koha::Exception;
33
34 use MARC::Field;
35 use MARC::Record;
36
37 use t::lib::TestBuilder;
38 use t::lib::Mocks;
39 use Test::MockModule;
40
41 BEGIN {
42     use_ok('Koha::Biblio');
43     use_ok('Koha::Biblios');
44 }
45
46 my $schema  = Koha::Database->new->schema;
47 my $builder = t::lib::TestBuilder->new;
48
49 subtest 'metadata() tests' => sub {
50
51     plan tests => 4;
52
53     $schema->storage->txn_begin;
54
55     my $title = 'Oranges and Peaches';
56
57     my $record = MARC::Record->new();
58     my $field = MARC::Field->new('245','','','a' => $title);
59     $record->append_fields( $field );
60     my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
61
62     my $biblio = Koha::Biblios->find( $biblionumber );
63     is( ref $biblio, 'Koha::Biblio', 'Found a Koha::Biblio object' );
64
65     my $metadata = $biblio->metadata;
66     is( ref $metadata, 'Koha::Biblio::Metadata', 'Method metadata() returned a Koha::Biblio::Metadata object' );
67
68     my $record2 = $metadata->record;
69     is( ref $record2, 'MARC::Record', 'Method record() returned a MARC::Record object' );
70
71     is( $record2->field('245')->subfield("a"), $title, 'Title in 245$a matches title from original record object' );
72
73     $schema->storage->txn_rollback;
74 };
75
76 subtest 'hidden_in_opac() tests' => sub {
77
78     plan tests => 6;
79
80     $schema->storage->txn_begin;
81
82     my $biblio = $builder->build_sample_biblio();
83     my $rules  = { withdrawn => [ 2 ] };
84
85     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
86
87     ok(
88         !$biblio->hidden_in_opac({ rules => $rules }),
89         'Biblio not hidden if there is no item attached (!OpacHiddenItemsHidesRecord)'
90     );
91
92     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 1 );
93
94     ok(
95         !$biblio->hidden_in_opac({ rules => $rules }),
96         'Biblio not hidden if there is no item attached (OpacHiddenItemsHidesRecord)'
97     );
98
99     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
100     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
101
102     $item_1->withdrawn( 1 )->store->discard_changes;
103     $item_2->withdrawn( 1 )->store->discard_changes;
104
105     ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
106
107     $item_2->withdrawn( 2 )->store->discard_changes;
108     $biblio->discard_changes; # refresh
109
110     ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
111
112     $item_1->withdrawn( 2 )->store->discard_changes;
113     $biblio->discard_changes; # refresh
114
115     ok( $biblio->hidden_in_opac({ rules => $rules }), 'Biblio hidden' );
116
117     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
118     ok(
119         !$biblio->hidden_in_opac( { rules => $rules } ),
120         'Biblio hidden (!OpacHiddenItemsHidesRecord)'
121     );
122
123
124     $schema->storage->txn_rollback;
125 };
126
127 subtest 'items() tests' => sub {
128
129     plan tests => 3;
130
131     $schema->storage->txn_begin;
132
133     my $biblio = $builder->build_sample_biblio();
134
135     is( $biblio->items->count, 0, 'No items, count is 0' );
136
137     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
138     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
139
140     my $items = $biblio->items;
141     is( ref($items), 'Koha::Items', 'Returns a Koha::Items resultset' );
142     is( $items->count, 2, 'Two items in resultset' );
143
144     $schema->storage->txn_rollback;
145
146 };
147
148 subtest 'get_coins and get_openurl' => sub {
149
150     plan tests => 4;
151
152     $schema->storage->txn_begin;
153
154     my $builder = t::lib::TestBuilder->new;
155     my $biblio = $builder->build_sample_biblio({
156             title => 'Title 1',
157             author => 'Author 1'
158         });
159     is(
160         $biblio->get_coins,
161         'ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
162         'GetCOinsBiblio returned right metadata'
163     );
164
165     my $record = MARC::Record->new();
166     $record->append_fields( MARC::Field->new('100','','','a' => 'Author 2'), MARC::Field->new('880','','','a' => 'Something') );
167     my ( $biblionumber ) = C4::Biblio::AddBiblio($record, '');
168     my $biblio_no_title = Koha::Biblios->find($biblionumber);
169     is(
170         $biblio_no_title->get_coins,
171         'ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.au=Author%202',
172         'GetCOinsBiblio returned right metadata if biblio does not have a title'
173     );
174
175     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/");
176     is(
177         $biblio->get_openurl,
178         'https://koha.example.com/?ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
179         'Koha::Biblio->get_openurl returned right URL'
180     );
181
182     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/?client_id=ci1");
183     is(
184         $biblio->get_openurl,
185         'https://koha.example.com/?client_id=ci1&amp;ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
186         'Koha::Biblio->get_openurl returned right URL'
187     );
188
189     $schema->storage->txn_rollback;
190 };
191
192 subtest 'is_serial() tests' => sub {
193
194     plan tests => 3;
195
196     $schema->storage->txn_begin;
197
198     my $biblio = $builder->build_sample_biblio();
199
200     $biblio->serial( 1 )->store->discard_changes;
201     ok( $biblio->is_serial, 'Bibliographic record is serial' );
202
203     $biblio->serial( 0 )->store->discard_changes;
204     ok( !$biblio->is_serial, 'Bibliographic record is not serial' );
205
206     my $record = $biblio->metadata->record;
207     $record->leader('00142nas a22     7a 4500');
208     ModBiblio($record, $biblio->biblionumber );
209     $biblio = Koha::Biblios->find($biblio->biblionumber);
210
211     ok( $biblio->is_serial, 'Bibliographic record is serial' );
212
213     $schema->storage->txn_rollback;
214 };
215
216 subtest 'pickup_locations' => sub {
217     plan tests => 9;
218
219     $schema->storage->txn_begin;
220
221     Koha::CirculationRules->search->delete;
222     Koha::CirculationRules->set_rules(
223         {
224             categorycode => undef,
225             itemtype     => undef,
226             branchcode   => undef,
227             rules        => {
228                 reservesallowed => 25,
229             }
230         }
231     );
232
233     my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
234     my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
235     my $root3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
236
237     my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'zzz' } } );
238     my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'AAA' } } );
239     my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'FFF' } } );
240     my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'CCC' } } );
241     my $library5 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'eee' } } );
242     my $library6 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'BBB' } } );
243     my $library7 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'DDD' } } );
244     my $library8 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'GGG' } } );
245
246     our @branchcodes = map { $_->branchcode } ($library1, $library2, $library3, $library4, $library5, $library6, $library7, $library8);
247
248     Koha::CirculationRules->set_rules(
249         {
250             branchcode => $library1->branchcode,
251             itemtype   => undef,
252             rules => {
253                 holdallowed => 'from_home_library',
254                 hold_fulfillment_policy => 'any',
255                 returnbranch => 'any'
256             }
257         }
258     );
259
260     Koha::CirculationRules->set_rules(
261         {
262             branchcode => $library2->branchcode,
263             itemtype   => undef,
264             rules => {
265                 holdallowed => 'from_local_hold_group',
266                 hold_fulfillment_policy => 'holdgroup',
267                 returnbranch => 'any'
268             }
269         }
270     );
271
272     Koha::CirculationRules->set_rules(
273         {
274             branchcode => $library3->branchcode,
275             itemtype   => undef,
276             rules => {
277                 holdallowed => 'from_local_hold_group',
278                 hold_fulfillment_policy => 'patrongroup',
279                 returnbranch => 'any'
280             }
281         }
282     );
283
284     Koha::CirculationRules->set_rules(
285         {
286             branchcode => $library4->branchcode,
287             itemtype   => undef,
288             rules => {
289                 holdallowed => 'from_any_library',
290                 hold_fulfillment_policy => 'holdingbranch',
291                 returnbranch => 'any'
292             }
293         }
294     );
295
296     Koha::CirculationRules->set_rules(
297         {
298             branchcode => $library5->branchcode,
299             itemtype   => undef,
300             rules => {
301                 holdallowed => 'from_any_library',
302                 hold_fulfillment_policy => 'homebranch',
303                 returnbranch => 'any'
304             }
305         }
306     );
307
308     Koha::CirculationRules->set_rules(
309         {
310             branchcode => $library6->branchcode,
311             itemtype   => undef,
312             rules => {
313                 holdallowed => 'from_home_library',
314                 hold_fulfillment_policy => 'holdgroup',
315                 returnbranch => 'any'
316             }
317         }
318     );
319
320     Koha::CirculationRules->set_rules(
321         {
322             branchcode => $library7->branchcode,
323             itemtype   => undef,
324             rules => {
325                 holdallowed => 'from_local_hold_group',
326                 hold_fulfillment_policy => 'holdingbranch',
327                 returnbranch => 'any'
328             }
329         }
330     );
331
332
333     Koha::CirculationRules->set_rules(
334         {
335             branchcode => $library8->branchcode,
336             itemtype   => undef,
337             rules => {
338                 holdallowed => 'from_any_library',
339                 hold_fulfillment_policy => 'patrongroup',
340                 returnbranch => 'any'
341             }
342         }
343     );
344
345     my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
346     my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
347
348     my $group2_3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
349     my $group2_4 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
350
351     my $group3_5 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library5->branchcode } } );
352     my $group3_6 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library6->branchcode } } );
353     my $group3_7 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library7->branchcode } } );
354     my $group3_8 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library8->branchcode } } );
355
356     my $biblio1  = $builder->build_sample_biblio({ title => '1' });
357     my $biblio2  = $builder->build_sample_biblio({ title => '2' });
358
359     my $item1_1  = $builder->build_sample_item({
360         biblionumber     => $biblio1->biblionumber,
361         homebranch       => $library1->branchcode,
362         holdingbranch    => $library2->branchcode,
363     })->store;
364
365     my $item1_3  = $builder->build_sample_item({
366         biblionumber     => $biblio1->biblionumber,
367         homebranch       => $library3->branchcode,
368         holdingbranch    => $library4->branchcode,
369     })->store;
370
371     my $item1_7  = $builder->build_sample_item({
372         biblionumber     => $biblio1->biblionumber,
373         homebranch       => $library7->branchcode,
374         holdingbranch    => $library4->branchcode,
375     })->store;
376
377     my $item2_2  = $builder->build_sample_item({
378         biblionumber     => $biblio2->biblionumber,
379         homebranch       => $library2->branchcode,
380         holdingbranch    => $library1->branchcode,
381     })->store;
382
383     my $item2_4  = $builder->build_sample_item({
384         biblionumber     => $biblio2->biblionumber,
385         homebranch       => $library4->branchcode,
386         holdingbranch    => $library3->branchcode,
387     })->store;
388
389     my $item2_6  = $builder->build_sample_item({
390         biblionumber     => $biblio2->biblionumber,
391         homebranch       => $library6->branchcode,
392         holdingbranch    => $library4->branchcode,
393     })->store;
394
395     my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'1', branchcode => $library1->branchcode } } );
396     my $patron8 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'8', branchcode => $library8->branchcode } } );
397
398     my $results = {
399         "ItemHomeLibrary-1-1" => 6,
400         "ItemHomeLibrary-1-8" => 1,
401         "ItemHomeLibrary-2-1" => 2,
402         "ItemHomeLibrary-2-8" => 0,
403         "PatronLibrary-1-1" => 6,
404         "PatronLibrary-1-8" => 3,
405         "PatronLibrary-2-1" => 0,
406         "PatronLibrary-2-8" => 3,
407     };
408
409     sub _doTest {
410         my ( $cbranch, $biblio, $patron, $results ) = @_;
411         t::lib::Mocks::mock_preference('ReservesControlBranch', $cbranch);
412
413         my @pl = map {
414             my $pickup_location = $_;
415             grep { $pickup_location->branchcode eq $_ } @branchcodes
416         } $biblio->pickup_locations( { patron => $patron } )->as_list;
417
418         ok(
419             scalar(@pl) == $results->{ $cbranch . '-'
420                   . $biblio->title . '-'
421                   . $patron->firstname },
422             'ReservesControlBranch: '
423               . $cbranch
424               . ', biblio'
425               . $biblio->title
426               . ', patron'
427               . $patron->firstname
428               . ' should return '
429               . $results->{ $cbranch . '-'
430                   . $biblio->title . '-'
431                   . $patron->firstname }
432               . ' but returns '
433               . scalar(@pl)
434         );
435     }
436
437     foreach my $cbranch ('ItemHomeLibrary','PatronLibrary') {
438         foreach my $biblio ($biblio1, $biblio2) {
439             foreach my $patron ($patron1, $patron8) {
440                 _doTest($cbranch, $biblio, $patron, $results);
441             }
442         }
443     }
444
445     my @pl_names = map { $_->branchname } $biblio1->pickup_locations( { patron => $patron1 } )->as_list;
446     my $pl_ori_str = join('|', @pl_names);
447     my $pl_sorted_str = join('|', sort { lc($a) cmp lc($b) } @pl_names);
448     ok(
449         $pl_ori_str eq $pl_sorted_str,
450         'Libraries must be sorted by name'
451     );
452     $schema->storage->txn_rollback;
453 };
454
455 subtest 'to_api() tests' => sub {
456
457     $schema->storage->txn_begin;
458
459     my $biblio = $builder->build_sample_biblio();
460     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
461
462     my $biblioitem_api = $biblio->biblioitem->to_api;
463     my $biblio_api     = $biblio->to_api;
464
465     plan tests => (scalar keys %{ $biblioitem_api }) + 1;
466
467     foreach my $key ( keys %{ $biblioitem_api } ) {
468         is( $biblio_api->{$key}, $biblioitem_api->{$key}, "$key is added to the biblio object" );
469     }
470
471     $biblio_api = $biblio->to_api({ embed => { items => {} } });
472     is_deeply( $biblio_api->{items}, [ $item->to_api ], 'Item correctly embedded' );
473
474     $schema->storage->txn_rollback;
475 };
476
477 subtest 'suggestions() tests' => sub {
478
479     plan tests => 3;
480
481     $schema->storage->txn_begin;
482
483     my $biblio     = $builder->build_sample_biblio();
484
485     is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
486
487     is_deeply(
488         $biblio->suggestions->unblessed,
489         [],
490         '->suggestions returns an empty Koha::Suggestions resultset'
491     );
492
493     my $suggestion = $builder->build_object(
494         {
495             class => 'Koha::Suggestions',
496             value => { biblionumber => $biblio->biblionumber }
497         }
498     );
499
500     my $suggestions = $biblio->suggestions->unblessed;
501
502     is_deeply(
503         $biblio->suggestions->unblessed,
504         [ $suggestion->unblessed ],
505         '->suggestions returns the related Koha::Suggestion objects'
506     );
507
508     $schema->storage->txn_rollback;
509 };
510
511 subtest 'get_marc_components() tests' => sub {
512
513     plan tests => 5;
514
515     $schema->storage->txn_begin;
516
517     my ($host_bibnum) = C4::Biblio::AddBiblio(host_record(), '');
518     my $host_biblio = Koha::Biblios->find($host_bibnum);
519     t::lib::Mocks::mock_preference( 'SearchEngine', 'Zebra' );
520     my $search_mod = Test::MockModule->new( 'Koha::SearchEngine::Zebra::Search' );
521     $search_mod->mock( 'search_compat', \&search_component_record2 );
522
523     my $components = $host_biblio->get_marc_components;
524     is( ref($components), 'ARRAY', 'Return type is correct' );
525
526     is_deeply(
527         $components,
528         [],
529         '->get_marc_components returns an empty ARRAY'
530     );
531
532     $search_mod->unmock( 'search_compat');
533     $search_mod->mock( 'search_compat', \&search_component_record1 );
534     my $component_record = component_record1()->as_xml();
535
536     is_deeply(
537         $host_biblio->get_marc_components,
538         [$component_record],
539         '->get_marc_components returns the related component part record'
540     );
541     $search_mod->unmock( 'search_compat');
542
543     $search_mod->mock( 'search_compat',
544         sub { Koha::Exception->throw("error searching analytics") }
545     );
546     warning_like { $components = $host_biblio->get_marc_components }
547         qr{Warning from search_compat: .* 'error searching analytics'};
548
549     is_deeply(
550         $host_biblio->object_messages,
551         [
552             {
553                 type    => 'error',
554                 message => 'component_search',
555                 payload => "Exception 'Koha::Exception' thrown 'error searching analytics'\n"
556             }
557         ]
558     );
559     $search_mod->unmock( 'search_compat');
560
561     $schema->storage->txn_rollback;
562 };
563
564 subtest 'get_components_query' => sub {
565     plan tests => 6;
566
567     my $biblio = $builder->build_sample_biblio();
568     my $biblionumber = $biblio->biblionumber;
569     my $record = $biblio->metadata->record;
570
571     t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
572     t::lib::Mocks::mock_preference( 'ComponentSortField', 'author' );
573     t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'za' );
574     my ( $comp_q, $comp_s ) = $biblio->get_components_query;
575     is($comp_q, 'Host-item:("Some boring read")', "UseControlNumber disabled");
576     is($comp_s, "author_za", "UseControlNumber disabled sort is correct");
577
578     t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
579     t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'az' );
580     my $marc_001_field = MARC::Field->new('001', $biblionumber);
581     $record->append_fields($marc_001_field);
582     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
583     $biblio = Koha::Biblios->find( $biblio->biblionumber);
584
585     ( $comp_q, $comp_s ) = $biblio->get_components_query;
586     is($comp_q, "(rcn:$biblionumber AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled without MarcOrgCode");
587     is($comp_s, "author_az", "UseControlNumber enabled without MarcOrgCode sort is correct");
588
589     my $marc_003_field = MARC::Field->new('003', 'OSt');
590     $record->append_fields($marc_003_field);
591     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
592     $biblio = Koha::Biblios->find( $biblio->biblionumber);
593
594     t::lib::Mocks::mock_preference( 'ComponentSortField', 'title' );
595     t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'asc' );
596     ( $comp_q, $comp_s ) = $biblio->get_components_query;
597     is($comp_q, "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled with MarcOrgCode");
598     is($comp_s, "title_asc", "UseControlNumber enabled with MarcOrgCode sort if correct");
599 };
600
601 subtest 'orders() and active_orders() tests' => sub {
602
603     plan tests => 5;
604
605     $schema->storage->txn_begin;
606
607     my $biblio = $builder->build_sample_biblio();
608
609     my $orders        = $biblio->orders;
610     my $active_orders = $biblio->active_orders;
611
612     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
613     is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
614
615     # Add a couple orders
616     foreach (1..2) {
617         $builder->build_object(
618             {
619                 class => 'Koha::Acquisition::Orders',
620                 value => {
621                     biblionumber => $biblio->biblionumber,
622                     datecancellationprinted => '2019-12-31'
623                 }
624             }
625         );
626     }
627
628     $builder->build_object(
629         {
630             class => 'Koha::Acquisition::Orders',
631             value => {
632                 biblionumber => $biblio->biblionumber,
633                 datecancellationprinted => undef
634             }
635         }
636     );
637
638     $orders = $biblio->orders;
639     $active_orders = $biblio->active_orders;
640
641     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
642     is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
643     is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
644
645     $schema->storage->txn_rollback;
646 };
647
648 subtest 'subscriptions() tests' => sub {
649
650     plan tests => 4;
651
652     $schema->storage->txn_begin;
653
654     my $biblio = $builder->build_sample_biblio;
655
656     my $subscriptions = $biblio->subscriptions;
657     is( ref($subscriptions), 'Koha::Subscriptions',
658         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
659     );
660     is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
661
662     # Add two subscriptions
663     foreach (1..2) {
664         $builder->build_object(
665             {
666                 class => 'Koha::Subscriptions',
667                 value => { biblionumber => $biblio->biblionumber }
668             }
669         );
670     }
671
672     $subscriptions = $biblio->subscriptions;
673     is( ref($subscriptions), 'Koha::Subscriptions',
674         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
675     );
676     is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
677
678     $schema->storage->txn_rollback;
679 };
680
681 subtest 'get_marc_notes() MARC21 tests' => sub {
682     plan tests => 14;
683
684     $schema->storage->txn_begin;
685
686     t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
687
688     my $biblio = $builder->build_sample_biblio;
689     my $record = $biblio->metadata->record;
690     $record->append_fields(
691         MARC::Field->new( '500', '', '', a => 'Note1' ),
692         MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
693         MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
694         MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
695         MARC::Field->new( '544', '', '', a => 'Note5' ),
696         MARC::Field->new( '590', '', '', a => 'CODE' ),
697         MARC::Field->new( '545', '', '', a => 'Invisible on OPAC' ),
698     );
699
700     Koha::AuthorisedValueCategory->new({ category_name => 'TEST' })->store;
701     Koha::AuthorisedValue->new(
702         {
703             category         => 'TEST',
704             authorised_value => 'CODE',
705             lib              => 'Description should show',
706             lib_opac         => 'Description should show OPAC'
707         }
708     )->store;
709     my $mss = Koha::MarcSubfieldStructures->find({tagfield => "590", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
710     $mss->update({ authorised_value => "TEST" });
711
712     $mss = Koha::MarcSubfieldStructures->find({tagfield => "545", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
713     $mss->update({ hidden => 1 });
714
715     my $cache = Koha::Caches->get_instance;
716     $cache->clear_from_cache("MarcStructure-0-");
717     $cache->clear_from_cache("MarcStructure-1-");
718     $cache->clear_from_cache("default_value_for_mod_marc-");
719     $cache->clear_from_cache("MarcSubfieldStructure-");
720
721     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
722     $biblio = Koha::Biblios->find( $biblio->biblionumber);
723
724     my $notes = $biblio->get_marc_notes;
725     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
726     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
727     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
728     is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Note shows if not opac (Hidden by Indicator)" );
729     is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
730     is( $notes->[5]->{marcnote}, 'Description should show', 'Authorised value is correctly parsed to show description rather than code' );
731     is( $notes->[6]->{marcnote}, 'Invisible on OPAC', 'Note shows if not opac (Hidden by framework)' );
732     is( @$notes, 7, 'No more notes' );
733     $notes = $biblio->get_marc_notes({ opac => 1 });
734     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
735     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
736     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
737     is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
738     is( $notes->[4]->{marcnote}, 'Description should show OPAC', 'Authorised value is correctly parsed for OPAC to show description rather than code' );
739     is( @$notes, 5, 'No more notes' );
740
741     $cache->clear_from_cache("MarcStructure-0-");
742     $cache->clear_from_cache("MarcStructure-1-");
743     $cache->clear_from_cache("default_value_for_mod_marc-");
744     $cache->clear_from_cache("MarcSubfieldStructure-");
745
746     $schema->storage->txn_rollback;
747 };
748
749 subtest 'get_marc_notes() UNIMARC tests' => sub {
750     plan tests => 3;
751
752     $schema->storage->txn_begin;
753
754     t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
755     t::lib::Mocks::mock_preference( 'marcflavour', 'UNIMARC' );
756
757     my $biblio = $builder->build_sample_biblio;
758     my $record = $biblio->metadata->record;
759     $record->append_fields(
760         MARC::Field->new( '300', '', '', a => 'Note1' ),
761         MARC::Field->new( '300', '', '', a => 'Note2' ),
762         MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
763     );
764     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
765     $biblio = Koha::Biblios->find( $biblio->biblionumber);
766     my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
767     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
768     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
769     is( @$notes, 2, 'No more notes' );
770
771     t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
772     $schema->storage->txn_rollback;
773 };
774
775 subtest 'host_items() tests' => sub {
776     plan tests => 6;
777
778     $schema->storage->txn_begin;
779
780     my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
781
782     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
783     my $host_items = $biblio->host_items;
784     is( ref($host_items),   'Koha::Items' );
785     is( $host_items->count, 0 );
786
787     my $item_1 =
788       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
789     my $host_item_1 = $builder->build_sample_item;
790     my $host_item_2 = $builder->build_sample_item;
791
792     my $record = $biblio->metadata->record;
793     $record->append_fields(
794         MARC::Field->new(
795             '773', '', '',
796             9 => $host_item_1->itemnumber,
797             9 => $host_item_2->itemnumber
798         ),
799     );
800     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
801     $biblio = $biblio->get_from_storage;
802     $host_items = $biblio->host_items;
803     is( $host_items->count, 2 );
804     is_deeply( [ $host_items->get_column('itemnumber') ],
805         [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
806
807     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
808     $host_items = $biblio->host_items;
809     is( ref($host_items),   'Koha::Items' );
810     is( $host_items->count, 0 );
811
812     $schema->storage->txn_rollback;
813 };
814
815 subtest 'article_requests() tests' => sub {
816
817     plan tests => 3;
818
819     $schema->storage->txn_begin;
820
821     my $item   = $builder->build_sample_item;
822     my $biblio = $item->biblio;
823
824     my $article_requests = $biblio->article_requests;
825     is( ref($article_requests), 'Koha::ArticleRequests',
826         'In scalar context, type is correct' );
827     is( $article_requests->count, 0, 'No article requests' );
828
829     foreach my $i ( 0 .. 3 ) {
830
831         my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
832
833         Koha::ArticleRequest->new(
834             {
835                 borrowernumber => $patron->id,
836                 biblionumber   => $biblio->id,
837                 itemnumber     => $item->id,
838                 title          => $biblio->title,
839             }
840         )->request;
841     }
842
843     $article_requests = $biblio->article_requests;
844     is( $article_requests->count, 4, '4 article requests' );
845
846     $schema->storage->txn_rollback;
847 };
848
849 subtest 'current_checkouts() and old_checkouts() tests' => sub {
850
851     plan tests => 4;
852
853     $schema->storage->txn_begin;
854
855     my $library = $builder->build_object({ class => 'Koha::Libraries' });
856
857     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
858     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
859
860     my $item_1 = $builder->build_sample_item;
861     my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
862
863     t::lib::Mocks::mock_userenv({ branchcode => $library->id });
864
865     AddIssue( $patron_1, $item_1->barcode );
866     AddIssue( $patron_1, $item_2->barcode );
867
868     AddReturn( $item_1->barcode );
869     AddIssue( $patron_2, $item_1->barcode );
870
871     my $biblio = $item_1->biblio;
872     my $current_checkouts = $biblio->current_checkouts;
873     my $old_checkouts = $biblio->old_checkouts;
874
875     is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
876     is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
877
878     is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
879     is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
880
881     $schema->storage->txn_rollback;
882 };
883
884 subtest 'get_marc_authors() tests' => sub {
885
886     plan tests => 1;
887
888     $schema->storage->txn_begin;
889
890     my $biblio = $builder->build_sample_biblio;
891     my $record = $biblio->metadata->record;
892
893     # add author information
894     my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
895     $record->append_fields($field);
896     $field = MARC::Field->new('700','1','','d' => '1743-1826');
897     $record->append_fields($field);
898     $field = MARC::Field->new('700','1','','e' => 'former owner.');
899     $record->append_fields($field);
900     $field = MARC::Field->new('700','1','','5' => 'MH');
901     $record->append_fields($field);
902
903     # get record
904     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
905     $biblio = Koha::Biblios->find( $biblio->biblionumber );
906
907     is( 4, @{$biblio->get_marc_authors}, 'get_marc_authors retrieves correct number of author subfields' );
908     $schema->storage->txn_rollback;
909 };
910
911 subtest 'Recalls tests' => sub {
912
913     plan tests => 13;
914
915     $schema->storage->txn_begin;
916
917     my $item1 = $builder->build_sample_item;
918     my $biblio = $item1->biblio;
919     my $branchcode = $item1->holdingbranch;
920     my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
921     my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
922     my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
923     my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
924     t::lib::Mocks::mock_userenv({ patron => $patron1 });
925
926     my $recall1 = Koha::Recall->new(
927         {   patron_id         => $patron1->borrowernumber,
928             created_date      => \'NOW()',
929             biblio_id         => $biblio->biblionumber,
930             pickup_library_id => $branchcode,
931             item_id           => $item1->itemnumber,
932             expiration_date   => undef,
933             item_level        => 1
934         }
935     )->store;
936     my $recall2 = Koha::Recall->new(
937         {   patron_id         => $patron2->borrowernumber,
938             created_date      => \'NOW()',
939             biblio_id         => $biblio->biblionumber,
940             pickup_library_id => $branchcode,
941             item_id           => undef,
942             expiration_date   => undef,
943             item_level        => 0
944         }
945     )->store;
946     my $recall3 = Koha::Recall->new(
947         {   patron_id         => $patron3->borrowernumber,
948             created_date      => \'NOW()',
949             biblio_id         => $biblio->biblionumber,
950             pickup_library_id => $branchcode,
951             item_id           => $item1->itemnumber,
952             expiration_date   => undef,
953             item_level        => 1
954         }
955     )->store;
956
957     my $recalls = $biblio->recalls;
958     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
959
960     $recall1->set_cancelled;
961     $recall2->set_expired({ interface => 'COMMANDLINE' });
962
963     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
964     is( $recalls->filter_by_current->count, 1, 'Correctly get number of active recalls for biblio' );
965
966     t::lib::Mocks::mock_preference('UseRecalls', 0);
967     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
968
969     t::lib::Mocks::mock_preference("UseRecalls", 1);
970     $item1->update({ notforloan => 1 });
971     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
972
973     $item1->update({ notforloan => 0 });
974     Koha::CirculationRules->set_rules({
975         branchcode => $branchcode,
976         categorycode => $patron1->categorycode,
977         itemtype => $item1->effective_itemtype,
978         rules => {
979             recalls_allowed => 0,
980             recalls_per_record => 1,
981             on_shelf_recalls => 'all',
982         },
983     });
984     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
985
986     Koha::CirculationRules->set_rules({
987         branchcode => $branchcode,
988         categorycode => $patron1->categorycode,
989         itemtype => $item1->effective_itemtype,
990         rules => {
991             recalls_allowed => 1,
992             recalls_per_record => 1,
993             on_shelf_recalls => 'all',
994         },
995     });
996     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
997     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
998
999     $recall1->set_cancelled;
1000     C4::Circulation::AddIssue( $patron1->unblessed, $item2->barcode );
1001     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
1002
1003     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
1004
1005     Koha::CirculationRules->set_rules({
1006         branchcode => $branchcode,
1007         categorycode => $patron1->categorycode,
1008         itemtype => $item1->effective_itemtype,
1009         rules => {
1010             recalls_allowed => 1,
1011             recalls_per_record => 1,
1012             on_shelf_recalls => 'any',
1013         },
1014     });
1015     C4::Circulation::AddReturn( $item2->barcode, $branchcode );
1016     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
1017
1018     $recall2->set_cancelled;
1019     C4::Circulation::AddIssue( $patron2->unblessed, $item2->barcode );
1020     C4::Circulation::AddIssue( $patron2->unblessed, $item1->barcode );
1021     is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
1022
1023     $item1->update({ withdrawn => 1 });
1024     is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
1025
1026     $schema->storage->txn_rollback;
1027 };
1028
1029 subtest 'item_groups() tests' => sub {
1030
1031     plan tests => 6;
1032
1033     $schema->storage->txn_begin;
1034
1035     my $biblio = $builder->build_sample_biblio();
1036
1037     my @item_groups = $biblio->item_groups->as_list;
1038     is( scalar(@item_groups), 0, 'Got zero item groups');
1039
1040     my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1041
1042     @item_groups = $biblio->item_groups->as_list;
1043     is( scalar(@item_groups), 1, 'Got one item group');
1044     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
1045
1046     my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1047
1048     @item_groups = $biblio->item_groups->as_list;
1049     is( scalar(@item_groups), 2, 'Got two item groups');
1050     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
1051     is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
1052
1053     $schema->storage->txn_rollback;
1054 };
1055
1056 sub component_record1 {
1057     my $marc = MARC::Record->new;
1058     $marc->append_fields(
1059         MARC::Field->new( '001', '3456' ),
1060         MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1061         MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1062     );
1063     return $marc;
1064 }
1065 sub search_component_record1 {
1066     my @results = ( component_record1()->as_xml() );
1067     return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
1068 }
1069
1070 sub search_component_record2 {
1071     my @results;
1072     return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
1073 }
1074
1075 sub host_record {
1076     my $marc = MARC::Record->new;
1077     $marc->append_fields(
1078         MARC::Field->new( '001', '1234' ),
1079         MARC::Field->new( '003', 'FIRST' ),
1080         MARC::Field->new( '245', '', '', a => 'Some title' ),
1081     );
1082     return $marc;
1083 }