Bug 31196: Remove 'default_value_for_mod_marc-' clear_from_cache calls
[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("MarcSubfieldStructure-");
719
720     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
721     $biblio = Koha::Biblios->find( $biblio->biblionumber);
722
723     my $notes = $biblio->get_marc_notes;
724     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
725     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
726     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
727     is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Note shows if not opac (Hidden by Indicator)" );
728     is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
729     is( $notes->[5]->{marcnote}, 'Description should show', 'Authorised value is correctly parsed to show description rather than code' );
730     is( $notes->[6]->{marcnote}, 'Invisible on OPAC', 'Note shows if not opac (Hidden by framework)' );
731     is( @$notes, 7, 'No more notes' );
732     $notes = $biblio->get_marc_notes({ opac => 1 });
733     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
734     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
735     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
736     is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
737     is( $notes->[4]->{marcnote}, 'Description should show OPAC', 'Authorised value is correctly parsed for OPAC to show description rather than code' );
738     is( @$notes, 5, 'No more notes' );
739
740     $cache->clear_from_cache("MarcStructure-0-");
741     $cache->clear_from_cache("MarcStructure-1-");
742     $cache->clear_from_cache("MarcSubfieldStructure-");
743
744     $schema->storage->txn_rollback;
745 };
746
747 subtest 'get_marc_notes() UNIMARC tests' => sub {
748     plan tests => 3;
749
750     $schema->storage->txn_begin;
751
752     t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
753     t::lib::Mocks::mock_preference( 'marcflavour', 'UNIMARC' );
754
755     my $biblio = $builder->build_sample_biblio;
756     my $record = $biblio->metadata->record;
757     $record->append_fields(
758         MARC::Field->new( '300', '', '', a => 'Note1' ),
759         MARC::Field->new( '300', '', '', a => 'Note2' ),
760         MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
761     );
762     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
763     $biblio = Koha::Biblios->find( $biblio->biblionumber);
764     my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
765     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
766     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
767     is( @$notes, 2, 'No more notes' );
768
769     t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
770     $schema->storage->txn_rollback;
771 };
772
773 subtest 'host_items() tests' => sub {
774     plan tests => 6;
775
776     $schema->storage->txn_begin;
777
778     my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
779
780     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
781     my $host_items = $biblio->host_items;
782     is( ref($host_items),   'Koha::Items' );
783     is( $host_items->count, 0 );
784
785     my $item_1 =
786       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
787     my $host_item_1 = $builder->build_sample_item;
788     my $host_item_2 = $builder->build_sample_item;
789
790     my $record = $biblio->metadata->record;
791     $record->append_fields(
792         MARC::Field->new(
793             '773', '', '',
794             9 => $host_item_1->itemnumber,
795             9 => $host_item_2->itemnumber
796         ),
797     );
798     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
799     $biblio = $biblio->get_from_storage;
800     $host_items = $biblio->host_items;
801     is( $host_items->count, 2 );
802     is_deeply( [ $host_items->get_column('itemnumber') ],
803         [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
804
805     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
806     $host_items = $biblio->host_items;
807     is( ref($host_items),   'Koha::Items' );
808     is( $host_items->count, 0 );
809
810     $schema->storage->txn_rollback;
811 };
812
813 subtest 'article_requests() tests' => sub {
814
815     plan tests => 3;
816
817     $schema->storage->txn_begin;
818
819     my $item   = $builder->build_sample_item;
820     my $biblio = $item->biblio;
821
822     my $article_requests = $biblio->article_requests;
823     is( ref($article_requests), 'Koha::ArticleRequests',
824         'In scalar context, type is correct' );
825     is( $article_requests->count, 0, 'No article requests' );
826
827     foreach my $i ( 0 .. 3 ) {
828
829         my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
830
831         Koha::ArticleRequest->new(
832             {
833                 borrowernumber => $patron->id,
834                 biblionumber   => $biblio->id,
835                 itemnumber     => $item->id,
836                 title          => $biblio->title,
837             }
838         )->request;
839     }
840
841     $article_requests = $biblio->article_requests;
842     is( $article_requests->count, 4, '4 article requests' );
843
844     $schema->storage->txn_rollback;
845 };
846
847 subtest 'current_checkouts() and old_checkouts() tests' => sub {
848
849     plan tests => 4;
850
851     $schema->storage->txn_begin;
852
853     my $library = $builder->build_object({ class => 'Koha::Libraries' });
854
855     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
856     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
857
858     my $item_1 = $builder->build_sample_item;
859     my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
860
861     t::lib::Mocks::mock_userenv({ branchcode => $library->id });
862
863     AddIssue( $patron_1, $item_1->barcode );
864     AddIssue( $patron_1, $item_2->barcode );
865
866     AddReturn( $item_1->barcode );
867     AddIssue( $patron_2, $item_1->barcode );
868
869     my $biblio = $item_1->biblio;
870     my $current_checkouts = $biblio->current_checkouts;
871     my $old_checkouts = $biblio->old_checkouts;
872
873     is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
874     is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
875
876     is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
877     is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
878
879     $schema->storage->txn_rollback;
880 };
881
882 subtest 'get_marc_contributors() tests' => sub {
883
884     plan tests => 2;
885
886     $schema->storage->txn_begin;
887
888     my $biblio = $builder->build_sample_biblio({ author => 'Main author' });
889     my $record = $biblio->metadata->record;
890
891     # add author information
892     my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
893     $record->append_fields($field);
894     $field = MARC::Field->new('701','1','','d' => 'Secondary author 2');
895     $record->append_fields($field);
896
897     # get record
898     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
899     $biblio = Koha::Biblios->find( $biblio->biblionumber );
900
901     is( @{$biblio->get_marc_authors}, 3, 'get_marc_authors retrieves correct number of author subfields' );
902     is( @{$biblio->get_marc_contributors}, 2, 'get_marc_contributors retrieves correct number of author subfields' );
903     $schema->storage->txn_rollback;
904 };
905
906 subtest 'Recalls tests' => sub {
907
908     plan tests => 13;
909
910     $schema->storage->txn_begin;
911
912     my $item1 = $builder->build_sample_item;
913     my $biblio = $item1->biblio;
914     my $branchcode = $item1->holdingbranch;
915     my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
916     my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
917     my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
918     my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
919     t::lib::Mocks::mock_userenv({ patron => $patron1 });
920
921     my $recall1 = Koha::Recall->new(
922         {   patron_id         => $patron1->borrowernumber,
923             created_date      => \'NOW()',
924             biblio_id         => $biblio->biblionumber,
925             pickup_library_id => $branchcode,
926             item_id           => $item1->itemnumber,
927             expiration_date   => undef,
928             item_level        => 1
929         }
930     )->store;
931     my $recall2 = Koha::Recall->new(
932         {   patron_id         => $patron2->borrowernumber,
933             created_date      => \'NOW()',
934             biblio_id         => $biblio->biblionumber,
935             pickup_library_id => $branchcode,
936             item_id           => undef,
937             expiration_date   => undef,
938             item_level        => 0
939         }
940     )->store;
941     my $recall3 = Koha::Recall->new(
942         {   patron_id         => $patron3->borrowernumber,
943             created_date      => \'NOW()',
944             biblio_id         => $biblio->biblionumber,
945             pickup_library_id => $branchcode,
946             item_id           => $item1->itemnumber,
947             expiration_date   => undef,
948             item_level        => 1
949         }
950     )->store;
951
952     my $recalls = $biblio->recalls;
953     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
954
955     $recall1->set_cancelled;
956     $recall2->set_expired({ interface => 'COMMANDLINE' });
957
958     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
959     is( $recalls->filter_by_current->count, 1, 'Correctly get number of active recalls for biblio' );
960
961     t::lib::Mocks::mock_preference('UseRecalls', 0);
962     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
963
964     t::lib::Mocks::mock_preference("UseRecalls", 1);
965     $item1->update({ notforloan => 1 });
966     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
967
968     $item1->update({ notforloan => 0 });
969     Koha::CirculationRules->set_rules({
970         branchcode => $branchcode,
971         categorycode => $patron1->categorycode,
972         itemtype => $item1->effective_itemtype,
973         rules => {
974             recalls_allowed => 0,
975             recalls_per_record => 1,
976             on_shelf_recalls => 'all',
977         },
978     });
979     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
980
981     Koha::CirculationRules->set_rules({
982         branchcode => $branchcode,
983         categorycode => $patron1->categorycode,
984         itemtype => $item1->effective_itemtype,
985         rules => {
986             recalls_allowed => 1,
987             recalls_per_record => 1,
988             on_shelf_recalls => 'all',
989         },
990     });
991     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
992     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
993
994     $recall1->set_cancelled;
995     C4::Circulation::AddIssue( $patron1->unblessed, $item2->barcode );
996     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
997
998     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
999
1000     Koha::CirculationRules->set_rules({
1001         branchcode => $branchcode,
1002         categorycode => $patron1->categorycode,
1003         itemtype => $item1->effective_itemtype,
1004         rules => {
1005             recalls_allowed => 1,
1006             recalls_per_record => 1,
1007             on_shelf_recalls => 'any',
1008         },
1009     });
1010     C4::Circulation::AddReturn( $item2->barcode, $branchcode );
1011     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
1012
1013     $recall2->set_cancelled;
1014     C4::Circulation::AddIssue( $patron2->unblessed, $item2->barcode );
1015     C4::Circulation::AddIssue( $patron2->unblessed, $item1->barcode );
1016     is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
1017
1018     $item1->update({ withdrawn => 1 });
1019     is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
1020
1021     $schema->storage->txn_rollback;
1022 };
1023
1024 subtest 'item_groups() tests' => sub {
1025
1026     plan tests => 6;
1027
1028     $schema->storage->txn_begin;
1029
1030     my $biblio = $builder->build_sample_biblio();
1031
1032     my @item_groups = $biblio->item_groups->as_list;
1033     is( scalar(@item_groups), 0, 'Got zero item groups');
1034
1035     my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1036
1037     @item_groups = $biblio->item_groups->as_list;
1038     is( scalar(@item_groups), 1, 'Got one item group');
1039     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
1040
1041     my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1042
1043     @item_groups = $biblio->item_groups->as_list;
1044     is( scalar(@item_groups), 2, 'Got two item groups');
1045     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
1046     is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
1047
1048     $schema->storage->txn_rollback;
1049 };
1050
1051 sub component_record1 {
1052     my $marc = MARC::Record->new;
1053     $marc->append_fields(
1054         MARC::Field->new( '001', '3456' ),
1055         MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1056         MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1057     );
1058     return $marc;
1059 }
1060 sub search_component_record1 {
1061     my @results = ( component_record1()->as_xml() );
1062     return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
1063 }
1064
1065 sub search_component_record2 {
1066     my @results;
1067     return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
1068 }
1069
1070 sub host_record {
1071     my $marc = MARC::Record->new;
1072     $marc->append_fields(
1073         MARC::Field->new( '001', '1234' ),
1074         MARC::Field->new( '003', 'FIRST' ),
1075         MARC::Field->new( '245', '', '', a => 'Some title' ),
1076     );
1077     return $marc;
1078 }