Bug 19532: (follow-up) aria-hidden attr on OPAC, and more
[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 => 21; # +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( 'simple_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( 'simple_search_compat');
533     $search_mod->mock( 'simple_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( 'simple_search_compat');
542
543     $search_mod->mock( 'simple_search_compat',
544         sub { Koha::Exception->throw("error searching analytics") }
545     );
546     warning_like { $components = $host_biblio->get_marc_components }
547         qr{Warning from simple_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( 'simple_search_compat');
560
561     $schema->storage->txn_rollback;
562 };
563
564 subtest 'get_components_query' => sub {
565     plan tests => 3;
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     is($biblio->get_components_query, "Host-item:(Some boring read)", "UseControlNumber disabled");
573
574     t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
575     my $marc_001_field = MARC::Field->new('001', $biblionumber);
576     $record->append_fields($marc_001_field);
577     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
578     $biblio = Koha::Biblios->find( $biblio->biblionumber);
579
580     is($biblio->get_components_query, "(rcn:$biblionumber AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled without MarcOrgCode");
581
582     my $marc_003_field = MARC::Field->new('003', 'OSt');
583     $record->append_fields($marc_003_field);
584     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
585     $biblio = Koha::Biblios->find( $biblio->biblionumber);
586
587     is($biblio->get_components_query, "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "UseControlNumber enabled with MarcOrgCode");
588 };
589
590 subtest 'orders() and active_orders() tests' => sub {
591
592     plan tests => 5;
593
594     $schema->storage->txn_begin;
595
596     my $biblio = $builder->build_sample_biblio();
597
598     my $orders        = $biblio->orders;
599     my $active_orders = $biblio->active_orders;
600
601     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
602     is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
603
604     # Add a couple orders
605     foreach (1..2) {
606         $builder->build_object(
607             {
608                 class => 'Koha::Acquisition::Orders',
609                 value => {
610                     biblionumber => $biblio->biblionumber,
611                     datecancellationprinted => '2019-12-31'
612                 }
613             }
614         );
615     }
616
617     $builder->build_object(
618         {
619             class => 'Koha::Acquisition::Orders',
620             value => {
621                 biblionumber => $biblio->biblionumber,
622                 datecancellationprinted => undef
623             }
624         }
625     );
626
627     $orders = $biblio->orders;
628     $active_orders = $biblio->active_orders;
629
630     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
631     is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
632     is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
633
634     $schema->storage->txn_rollback;
635 };
636
637 subtest 'subscriptions() tests' => sub {
638
639     plan tests => 4;
640
641     $schema->storage->txn_begin;
642
643     my $biblio = $builder->build_sample_biblio;
644
645     my $subscriptions = $biblio->subscriptions;
646     is( ref($subscriptions), 'Koha::Subscriptions',
647         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
648     );
649     is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
650
651     # Add two subscriptions
652     foreach (1..2) {
653         $builder->build_object(
654             {
655                 class => 'Koha::Subscriptions',
656                 value => { biblionumber => $biblio->biblionumber }
657             }
658         );
659     }
660
661     $subscriptions = $biblio->subscriptions;
662     is( ref($subscriptions), 'Koha::Subscriptions',
663         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
664     );
665     is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
666
667     $schema->storage->txn_rollback;
668 };
669
670 subtest 'get_marc_notes() MARC21 tests' => sub {
671     plan tests => 13;
672
673     $schema->storage->txn_begin;
674
675     t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
676
677     my $biblio = $builder->build_sample_biblio;
678     my $record = $biblio->metadata->record;
679     $record->append_fields(
680         MARC::Field->new( '500', '', '', a => 'Note1' ),
681         MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
682         MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
683         MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
684         MARC::Field->new( '541', '', '', a => 'Note5' ),
685         MARC::Field->new( '590', '', '', a => 'CODE' ),
686     );
687
688     Koha::AuthorisedValueCategory->new({ category_name => 'TEST' })->store;
689     Koha::AuthorisedValue->new({ category => 'TEST', authorised_value => 'CODE', lib => 'Description should show', lib_opac => 'Description should show OPAC' })->store;
690     my $mss = Koha::MarcSubfieldStructures->find({tagfield => "590", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
691     $mss->update({ authorised_value => "TEST" });
692
693     my $cache = Koha::Caches->get_instance;
694     $cache->clear_from_cache("MarcStructure-0-");
695     $cache->clear_from_cache("MarcStructure-1-");
696     $cache->clear_from_cache("default_value_for_mod_marc-");
697     $cache->clear_from_cache("MarcSubfieldStructure-");
698
699     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
700     $biblio = Koha::Biblios->find( $biblio->biblionumber);
701
702     my $notes = $biblio->get_marc_notes({ marcflavour => 'MARC21' });
703     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
704     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
705     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
706     is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Not shows if not opac" );
707     is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
708     is( $notes->[5]->{marcnote}, 'Description should show', 'Authorised value is correctly parsed to show description rather than code' );
709     is( @$notes, 6, 'No more notes' );
710     $notes = $biblio->get_marc_notes({ marcflavour => 'MARC21', opac => 1 });
711     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
712     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
713     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
714     is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
715     is( $notes->[4]->{marcnote}, 'Description should show OPAC', 'Authorised value is correctly parsed for OPAC to show description rather than code' );
716     is( @$notes, 5, 'No more notes' );
717
718     $cache->clear_from_cache("MarcStructure-0-");
719     $cache->clear_from_cache("MarcStructure-1-");
720     $cache->clear_from_cache("default_value_for_mod_marc-");
721     $cache->clear_from_cache("MarcSubfieldStructure-");
722
723     $schema->storage->txn_rollback;
724 };
725
726 subtest 'get_marc_notes() UNIMARC tests' => sub {
727     plan tests => 3;
728
729     $schema->storage->txn_begin;
730
731     t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
732
733     my $biblio = $builder->build_sample_biblio;
734     my $record = $biblio->metadata->record;
735     $record->append_fields(
736         MARC::Field->new( '300', '', '', a => 'Note1' ),
737         MARC::Field->new( '300', '', '', a => 'Note2' ),
738         MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
739     );
740     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
741     $biblio = Koha::Biblios->find( $biblio->biblionumber);
742     my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
743     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
744     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
745     is( @$notes, 2, 'No more notes' );
746
747     $schema->storage->txn_rollback;
748 };
749
750 subtest 'host_items() tests' => sub {
751     plan tests => 6;
752
753     $schema->storage->txn_begin;
754
755     my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
756
757     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
758     my $host_items = $biblio->host_items;
759     is( ref($host_items),   'Koha::Items' );
760     is( $host_items->count, 0 );
761
762     my $item_1 =
763       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
764     my $host_item_1 = $builder->build_sample_item;
765     my $host_item_2 = $builder->build_sample_item;
766
767     my $record = $biblio->metadata->record;
768     $record->append_fields(
769         MARC::Field->new(
770             '773', '', '',
771             9 => $host_item_1->itemnumber,
772             9 => $host_item_2->itemnumber
773         ),
774     );
775     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
776     $biblio = $biblio->get_from_storage;
777     $host_items = $biblio->host_items;
778     is( $host_items->count, 2 );
779     is_deeply( [ $host_items->get_column('itemnumber') ],
780         [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
781
782     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
783     $host_items = $biblio->host_items;
784     is( ref($host_items),   'Koha::Items' );
785     is( $host_items->count, 0 );
786
787     $schema->storage->txn_rollback;
788 };
789
790 subtest 'article_requests() tests' => sub {
791
792     plan tests => 3;
793
794     $schema->storage->txn_begin;
795
796     my $item   = $builder->build_sample_item;
797     my $biblio = $item->biblio;
798
799     my $article_requests = $biblio->article_requests;
800     is( ref($article_requests), 'Koha::ArticleRequests',
801         'In scalar context, type is correct' );
802     is( $article_requests->count, 0, 'No article requests' );
803
804     foreach my $i ( 0 .. 3 ) {
805
806         my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
807
808         Koha::ArticleRequest->new(
809             {
810                 borrowernumber => $patron->id,
811                 biblionumber   => $biblio->id,
812                 itemnumber     => $item->id,
813                 title          => $biblio->title,
814             }
815         )->request;
816     }
817
818     $article_requests = $biblio->article_requests;
819     is( $article_requests->count, 4, '4 article requests' );
820
821     $schema->storage->txn_rollback;
822 };
823
824 subtest 'current_checkouts() and old_checkouts() tests' => sub {
825
826     plan tests => 4;
827
828     $schema->storage->txn_begin;
829
830     my $library = $builder->build_object({ class => 'Koha::Libraries' });
831
832     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
833     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' })->unblessed;
834
835     my $item_1 = $builder->build_sample_item;
836     my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
837
838     t::lib::Mocks::mock_userenv({ branchcode => $library->id });
839
840     AddIssue( $patron_1, $item_1->barcode );
841     AddIssue( $patron_1, $item_2->barcode );
842
843     AddReturn( $item_1->barcode );
844     AddIssue( $patron_2, $item_1->barcode );
845
846     my $biblio = $item_1->biblio;
847     my $current_checkouts = $biblio->current_checkouts;
848     my $old_checkouts = $biblio->old_checkouts;
849
850     is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
851     is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
852
853     is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
854     is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
855
856     $schema->storage->txn_rollback;
857 };
858
859 subtest 'get_marc_authors() tests' => sub {
860
861     plan tests => 1;
862
863     $schema->storage->txn_begin;
864
865     my $biblio = $builder->build_sample_biblio;
866     my $record = $biblio->metadata->record;
867
868     # add author information
869     my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
870     $record->append_fields($field);
871     $field = MARC::Field->new('700','1','','d' => '1743-1826');
872     $record->append_fields($field);
873     $field = MARC::Field->new('700','1','','e' => 'former owner.');
874     $record->append_fields($field);
875     $field = MARC::Field->new('700','1','','5' => 'MH');
876     $record->append_fields($field);
877
878     # get record
879     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
880     $biblio = Koha::Biblios->find( $biblio->biblionumber );
881
882     is( 4, @{$biblio->get_marc_authors}, 'get_marc_authors retrieves correct number of author subfields' );
883     $schema->storage->txn_rollback;
884 };
885
886 subtest 'Recalls tests' => sub {
887
888     plan tests => 12;
889
890     $schema->storage->txn_begin;
891     my $item1 = $builder->build_sample_item;
892     my $biblio = $item1->biblio;
893     my $branchcode = $item1->holdingbranch;
894     my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
895     my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
896     my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
897     my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
898     t::lib::Mocks::mock_userenv({ patron => $patron1 });
899
900     my $recall1 = Koha::Recall->new({
901         borrowernumber => $patron1->borrowernumber,
902         recalldate => Koha::DateUtils::dt_from_string,
903         biblionumber => $biblio->biblionumber,
904         branchcode => $branchcode,
905         status => 'R',
906         itemnumber => $item1->itemnumber,
907         expirationdate => undef,
908         item_level_recall => 1
909     })->store;
910     my $recall2 = Koha::Recall->new({
911         borrowernumber => $patron2->borrowernumber,
912         recalldate => Koha::DateUtils::dt_from_string,
913         biblionumber => $biblio->biblionumber,
914         branchcode => $branchcode,
915         status => 'R',
916         itemnumber => undef,
917         expirationdate => undef,
918         item_level_recall => 0
919     })->store;
920     my $recall3 = Koha::Recall->new({
921         borrowernumber => $patron3->borrowernumber,
922         recalldate => Koha::DateUtils::dt_from_string,
923         biblionumber => $biblio->biblionumber,
924         branchcode => $branchcode,
925         status => 'R',
926         itemnumber => $item1->itemnumber,
927         expirationdate => undef,
928         item_level_recall => 1
929     })->store;
930
931     my $recalls_count = scalar $biblio->recalls;
932     is( $recalls_count, 3, 'Correctly get number of active recalls for biblio' );
933
934     $recall1->set_cancelled;
935     $recall2->set_expired({ interface => 'COMMANDLINE' });
936
937     $recalls_count = scalar $biblio->recalls;
938     is( $recalls_count, 1, 'Correctly get number of active recalls for biblio' );
939
940     t::lib::Mocks::mock_preference('UseRecalls', 0);
941     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
942
943     t::lib::Mocks::mock_preference("UseRecalls", 1);
944     $item1->update({ notforloan => 1 });
945     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
946
947     $item1->update({ notforloan => 0 });
948     Koha::CirculationRules->set_rules({
949         branchcode => $branchcode,
950         categorycode => $patron1->categorycode,
951         itemtype => $item1->effective_itemtype,
952         rules => {
953             recalls_allowed => 0,
954             recalls_per_record => 1,
955             on_shelf_recalls => 'all',
956         },
957     });
958     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
959
960     Koha::CirculationRules->set_rules({
961         branchcode => $branchcode,
962         categorycode => $patron1->categorycode,
963         itemtype => $item1->effective_itemtype,
964         rules => {
965             recalls_allowed => 1,
966             recalls_per_record => 1,
967             on_shelf_recalls => 'all',
968         },
969     });
970     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
971     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
972
973     $recall1->set_cancelled;
974     C4::Circulation::AddIssue( $patron1->unblessed, $item2->barcode );
975     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
976
977     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
978
979     Koha::CirculationRules->set_rules({
980         branchcode => $branchcode,
981         categorycode => $patron1->categorycode,
982         itemtype => $item1->effective_itemtype,
983         rules => {
984             recalls_allowed => 1,
985             recalls_per_record => 1,
986             on_shelf_recalls => 'any',
987         },
988     });
989     C4::Circulation::AddReturn( $item2->barcode, $branchcode );
990     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
991
992     $recall2->set_cancelled;
993     C4::Circulation::AddIssue( $patron2->unblessed, $item2->barcode );
994     C4::Circulation::AddIssue( $patron2->unblessed, $item1->barcode );
995     is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
996
997     $item1->update({ withdrawn => 1 });
998     is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
999
1000     $schema->storage->txn_rollback;
1001 };
1002
1003 sub component_record1 {
1004     my $marc = MARC::Record->new;
1005     $marc->append_fields(
1006         MARC::Field->new( '001', '3456' ),
1007         MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1008         MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1009     );
1010     return $marc;
1011 }
1012 sub search_component_record1 {
1013     my @results = ( component_record1()->as_xml() );
1014     return ( undef, \@results, 1 );
1015 }
1016
1017 sub search_component_record2 {
1018     my @results;
1019     return ( undef, \@results, 0 );
1020 }
1021
1022 sub host_record {
1023     my $marc = MARC::Record->new;
1024     $marc->append_fields(
1025         MARC::Field->new( '001', '1234' ),
1026         MARC::Field->new( '003', 'FIRST' ),
1027         MARC::Field->new( '245', '', '', a => 'Some title' ),
1028     );
1029     return $marc;
1030 }