Bug 33036: REST API: Merge biblio records implements merging of records
[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 => 34;
21 use Test::Exception;
22 use Test::Warn;
23
24 use C4::Biblio qw( AddBiblio ModBiblio ModBiblioMarc );
25 use C4::Circulation qw( AddIssue AddReturn );
26
27 use Koha::Database;
28 use Koha::DateUtils qw( dt_from_string );
29 use Koha::Cache::Memory::Lite;
30 use Koha::Caches;
31 use Koha::Acquisition::Orders;
32 use Koha::AuthorisedValueCategories;
33 use Koha::AuthorisedValues;
34 use Koha::MarcSubfieldStructures;
35 use Koha::Exception;
36
37 use MARC::Field;
38 use MARC::Record;
39
40 use t::lib::TestBuilder;
41 use t::lib::Mocks;
42 use Test::MockModule;
43
44 BEGIN {
45     use_ok('Koha::Biblio');
46     use_ok('Koha::Biblios');
47 }
48
49 my $schema  = Koha::Database->new->schema;
50 my $builder = t::lib::TestBuilder->new;
51
52 subtest 'metadata() tests' => sub {
53
54     plan tests => 4;
55
56     $schema->storage->txn_begin;
57
58     my $title = 'Oranges and Peaches';
59
60     my $record = MARC::Record->new();
61     my $field = MARC::Field->new('245','','','a' => $title);
62     $record->append_fields( $field );
63     my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
64
65     my $biblio = Koha::Biblios->find( $biblionumber );
66     is( ref $biblio, 'Koha::Biblio', 'Found a Koha::Biblio object' );
67
68     my $metadata = $biblio->metadata;
69     is( ref $metadata, 'Koha::Biblio::Metadata', 'Method metadata() returned a Koha::Biblio::Metadata object' );
70
71     my $record2 = $metadata->record;
72     is( ref $record2, 'MARC::Record', 'Method record() returned a MARC::Record object' );
73
74     is( $record2->field('245')->subfield("a"), $title, 'Title in 245$a matches title from original record object' );
75
76     $schema->storage->txn_rollback;
77 };
78
79 subtest 'hidden_in_opac() tests' => sub {
80
81     plan tests => 6;
82
83     $schema->storage->txn_begin;
84
85     my $biblio = $builder->build_sample_biblio();
86     my $rules  = { withdrawn => [ 2 ] };
87
88     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
89
90     ok(
91         !$biblio->hidden_in_opac({ rules => $rules }),
92         'Biblio not hidden if there is no item attached (!OpacHiddenItemsHidesRecord)'
93     );
94
95     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 1 );
96
97     ok(
98         !$biblio->hidden_in_opac({ rules => $rules }),
99         'Biblio not hidden if there is no item attached (OpacHiddenItemsHidesRecord)'
100     );
101
102     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
103     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
104
105     $item_1->withdrawn( 1 )->store->discard_changes;
106     $item_2->withdrawn( 1 )->store->discard_changes;
107
108     ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
109
110     $item_2->withdrawn( 2 )->store->discard_changes;
111     $biblio->discard_changes; # refresh
112
113     ok( !$biblio->hidden_in_opac({ rules => $rules }), 'Biblio not hidden' );
114
115     $item_1->withdrawn( 2 )->store->discard_changes;
116     $biblio->discard_changes; # refresh
117
118     ok( $biblio->hidden_in_opac({ rules => $rules }), 'Biblio hidden' );
119
120     t::lib::Mocks::mock_preference( 'OpacHiddenItemsHidesRecord', 0 );
121     ok(
122         !$biblio->hidden_in_opac( { rules => $rules } ),
123         'Biblio hidden (!OpacHiddenItemsHidesRecord)'
124     );
125
126
127     $schema->storage->txn_rollback;
128 };
129
130 subtest 'items() tests' => sub {
131
132     plan tests => 3;
133
134     $schema->storage->txn_begin;
135
136     my $biblio = $builder->build_sample_biblio();
137
138     is( $biblio->items->count, 0, 'No items, count is 0' );
139
140     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
141     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
142
143     my $items = $biblio->items;
144     is( ref($items), 'Koha::Items', 'Returns a Koha::Items resultset' );
145     is( $items->count, 2, 'Two items in resultset' );
146
147     $schema->storage->txn_rollback;
148
149 };
150
151 subtest 'bookable_items() tests' => sub {
152     plan tests => 3;
153
154     $schema->storage->txn_begin;
155
156     my $biblio = $builder->build_sample_biblio();
157
158     # bookable items
159     my $bookable_item1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 1 } );
160
161     # not bookable items
162     my $non_bookable_item1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 0 } );
163     my $non_bookable_item2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 0 } );
164
165     is( ref( $biblio->bookable_items ), 'Koha::Items', "bookable_items returns a Koha::Items resultset" );
166     is( $biblio->bookable_items->count, 1,             "bookable_items returns the correct number of items" );
167     is(
168         $biblio->bookable_items->next->itemnumber, $bookable_item1->itemnumber,
169         "bookable_items returned the correct item"
170     );
171
172     $schema->storage->txn_rollback;
173 };
174
175 subtest 'get_coins and get_openurl' => sub {
176
177     plan tests => 4;
178
179     $schema->storage->txn_begin;
180
181     my $builder = t::lib::TestBuilder->new;
182     my $biblio = $builder->build_sample_biblio({
183             title => 'Title 1',
184             author => 'Author 1'
185         });
186     is(
187         $biblio->get_coins,
188         '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',
189         'GetCOinsBiblio returned right metadata'
190     );
191
192     my $record = MARC::Record->new();
193     $record->append_fields( MARC::Field->new('100','','','a' => 'Author 2'), MARC::Field->new('880','','','a' => 'Something') );
194     my ( $biblionumber ) = C4::Biblio::AddBiblio($record, '');
195     my $biblio_no_title = Koha::Biblios->find($biblionumber);
196     is(
197         $biblio_no_title->get_coins,
198         'ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.au=Author%202',
199         'GetCOinsBiblio returned right metadata if biblio does not have a title'
200     );
201
202     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/");
203     is(
204         $biblio->get_openurl,
205         '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',
206         'Koha::Biblio->get_openurl returned right URL'
207     );
208
209     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/?client_id=ci1");
210     is(
211         $biblio->get_openurl,
212         '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',
213         'Koha::Biblio->get_openurl returned right URL'
214     );
215
216     $schema->storage->txn_rollback;
217 };
218
219 subtest 'is_serial() tests' => sub {
220
221     plan tests => 3;
222
223     $schema->storage->txn_begin;
224
225     my $biblio = $builder->build_sample_biblio();
226
227     $biblio->serial( 1 )->store->discard_changes;
228     ok( $biblio->is_serial, 'Bibliographic record is serial' );
229
230     $biblio->serial( 0 )->store->discard_changes;
231     ok( !$biblio->is_serial, 'Bibliographic record is not serial' );
232
233     my $record = $biblio->metadata->record;
234     $record->leader('00142nas a22     7a 4500');
235     ModBiblio($record, $biblio->biblionumber );
236     $biblio = Koha::Biblios->find($biblio->biblionumber);
237
238     ok( $biblio->is_serial, 'Bibliographic record is serial' );
239
240     $schema->storage->txn_rollback;
241 };
242
243 subtest 'pickup_locations() tests' => sub {
244
245     plan tests => 11;
246
247     $schema->storage->txn_begin;
248
249     Koha::CirculationRules->search->delete;
250     Koha::CirculationRules->set_rules(
251         {
252             categorycode => undef,
253             itemtype     => undef,
254             branchcode   => undef,
255             rules        => {
256                 reservesallowed => 25,
257             }
258         }
259     );
260
261     my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
262     my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
263     my $root3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
264
265     my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'zzz' } } );
266     my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'AAA' } } );
267     my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'FFF' } } );
268     my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'CCC' } } );
269     my $library5 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'eee' } } );
270     my $library6 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'BBB' } } );
271     my $library7 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'DDD' } } );
272     my $library8 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'GGG' } } );
273
274     our @branchcodes = map { $_->branchcode } ($library1, $library2, $library3, $library4, $library5, $library6, $library7, $library8);
275
276     Koha::CirculationRules->set_rules(
277         {
278             branchcode => $library1->branchcode,
279             itemtype   => undef,
280             rules => {
281                 holdallowed => 'from_home_library',
282                 hold_fulfillment_policy => 'any',
283                 returnbranch => 'any'
284             }
285         }
286     );
287
288     Koha::CirculationRules->set_rules(
289         {
290             branchcode => $library2->branchcode,
291             itemtype   => undef,
292             rules => {
293                 holdallowed => 'from_local_hold_group',
294                 hold_fulfillment_policy => 'holdgroup',
295                 returnbranch => 'any'
296             }
297         }
298     );
299
300     Koha::CirculationRules->set_rules(
301         {
302             branchcode => $library3->branchcode,
303             itemtype   => undef,
304             rules => {
305                 holdallowed => 'from_local_hold_group',
306                 hold_fulfillment_policy => 'patrongroup',
307                 returnbranch => 'any'
308             }
309         }
310     );
311
312     Koha::CirculationRules->set_rules(
313         {
314             branchcode => $library4->branchcode,
315             itemtype   => undef,
316             rules => {
317                 holdallowed => 'from_any_library',
318                 hold_fulfillment_policy => 'holdingbranch',
319                 returnbranch => 'any'
320             }
321         }
322     );
323
324     Koha::CirculationRules->set_rules(
325         {
326             branchcode => $library5->branchcode,
327             itemtype   => undef,
328             rules => {
329                 holdallowed => 'from_any_library',
330                 hold_fulfillment_policy => 'homebranch',
331                 returnbranch => 'any'
332             }
333         }
334     );
335
336     Koha::CirculationRules->set_rules(
337         {
338             branchcode => $library6->branchcode,
339             itemtype   => undef,
340             rules => {
341                 holdallowed => 'from_home_library',
342                 hold_fulfillment_policy => 'holdgroup',
343                 returnbranch => 'any'
344             }
345         }
346     );
347
348     Koha::CirculationRules->set_rules(
349         {
350             branchcode => $library7->branchcode,
351             itemtype   => undef,
352             rules => {
353                 holdallowed => 'from_local_hold_group',
354                 hold_fulfillment_policy => 'holdingbranch',
355                 returnbranch => 'any'
356             }
357         }
358     );
359
360
361     Koha::CirculationRules->set_rules(
362         {
363             branchcode => $library8->branchcode,
364             itemtype   => undef,
365             rules => {
366                 holdallowed => 'from_any_library',
367                 hold_fulfillment_policy => 'patrongroup',
368                 returnbranch => 'any'
369             }
370         }
371     );
372
373     my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
374     my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
375
376     my $group2_3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
377     my $group2_4 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
378
379     my $group3_5 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library5->branchcode } } );
380     my $group3_6 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library6->branchcode } } );
381     my $group3_7 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library7->branchcode } } );
382     my $group3_8 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library8->branchcode } } );
383
384     my $biblio1  = $builder->build_sample_biblio({ title => '1' });
385     my $biblio2  = $builder->build_sample_biblio({ title => '2' });
386
387     throws_ok
388       { $biblio1->pickup_locations }
389       'Koha::Exceptions::MissingParameter',
390       'Exception thrown on missing parameter';
391
392     is( $@->parameter, 'patron', 'Exception param correctly set' );
393
394     my $item1_1  = $builder->build_sample_item({
395         biblionumber     => $biblio1->biblionumber,
396         homebranch       => $library1->branchcode,
397         holdingbranch    => $library2->branchcode,
398     })->store;
399
400     my $item1_3  = $builder->build_sample_item({
401         biblionumber     => $biblio1->biblionumber,
402         homebranch       => $library3->branchcode,
403         holdingbranch    => $library4->branchcode,
404     })->store;
405
406     my $item1_7  = $builder->build_sample_item({
407         biblionumber     => $biblio1->biblionumber,
408         homebranch       => $library7->branchcode,
409         holdingbranch    => $library4->branchcode,
410     })->store;
411
412     my $item2_2  = $builder->build_sample_item({
413         biblionumber     => $biblio2->biblionumber,
414         homebranch       => $library2->branchcode,
415         holdingbranch    => $library1->branchcode,
416     })->store;
417
418     my $item2_4  = $builder->build_sample_item({
419         biblionumber     => $biblio2->biblionumber,
420         homebranch       => $library4->branchcode,
421         holdingbranch    => $library3->branchcode,
422     })->store;
423
424     my $item2_6  = $builder->build_sample_item({
425         biblionumber     => $biblio2->biblionumber,
426         homebranch       => $library6->branchcode,
427         holdingbranch    => $library4->branchcode,
428     })->store;
429
430     my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'1', branchcode => $library1->branchcode } } );
431     my $patron8 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'8', branchcode => $library8->branchcode } } );
432
433     my $results = {
434         "ItemHomeLibrary-1-1" => 6,
435         "ItemHomeLibrary-1-8" => 1,
436         "ItemHomeLibrary-2-1" => 2,
437         "ItemHomeLibrary-2-8" => 0,
438         "PatronLibrary-1-1" => 6,
439         "PatronLibrary-1-8" => 3,
440         "PatronLibrary-2-1" => 0,
441         "PatronLibrary-2-8" => 3,
442     };
443
444     sub _doTest {
445         my ( $cbranch, $biblio, $patron, $results ) = @_;
446         t::lib::Mocks::mock_preference('ReservesControlBranch', $cbranch);
447
448         my @pl = map {
449             my $pickup_location = $_;
450             grep { $pickup_location->branchcode eq $_ } @branchcodes
451         } $biblio->pickup_locations( { patron => $patron } )->as_list;
452
453         ok(
454             scalar(@pl) == $results->{ $cbranch . '-'
455                   . $biblio->title . '-'
456                   . $patron->firstname },
457             'ReservesControlBranch: '
458               . $cbranch
459               . ', biblio'
460               . $biblio->title
461               . ', patron'
462               . $patron->firstname
463               . ' should return '
464               . $results->{ $cbranch . '-'
465                   . $biblio->title . '-'
466                   . $patron->firstname }
467               . ' but returns '
468               . scalar(@pl)
469         );
470     }
471
472     foreach my $cbranch ('ItemHomeLibrary','PatronLibrary') {
473         my $cache = Koha::Cache::Memory::Lite->get_instance();
474         $cache->flush(); # needed since we change ReservesControlBranch
475         foreach my $biblio ($biblio1, $biblio2) {
476             foreach my $patron ($patron1, $patron8) {
477                 _doTest($cbranch, $biblio, $patron, $results);
478             }
479         }
480     }
481
482     my @pl_names = map { $_->branchname } $biblio1->pickup_locations( { patron => $patron1 } )->as_list;
483     my $pl_ori_str = join('|', @pl_names);
484     my $pl_sorted_str = join('|', sort { lc($a) cmp lc($b) } @pl_names);
485     ok(
486         $pl_ori_str eq $pl_sorted_str,
487         'Libraries must be sorted by name'
488     );
489     $schema->storage->txn_rollback;
490 };
491
492 subtest 'to_api() tests' => sub {
493
494     $schema->storage->txn_begin;
495
496     my $biblio = $builder->build_sample_biblio();
497     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
498
499     my $biblioitem_api = $biblio->biblioitem->to_api;
500     my $biblio_api     = $biblio->to_api;
501
502     plan tests => (scalar keys %{ $biblioitem_api }) + 1;
503
504     foreach my $key ( keys %{ $biblioitem_api } ) {
505         is( $biblio_api->{$key}, $biblioitem_api->{$key}, "$key is added to the biblio object" );
506     }
507
508     $biblio_api = $biblio->to_api({ embed => { items => {} } });
509     is_deeply( $biblio_api->{items}, [ $item->to_api ], 'Item correctly embedded' );
510
511     $schema->storage->txn_rollback;
512 };
513
514 subtest 'bookings() tests' => sub {
515
516     plan tests => 3;
517
518     $schema->storage->txn_begin;
519
520     my $biblio = $builder->build_sample_biblio();
521
522     is( ref( $biblio->bookings ), 'Koha::Bookings', 'Return type is correct' );
523
524     is_deeply(
525         $biblio->bookings->unblessed,
526         [],
527         '->bookings returns an empty Koha::Bookings resultset'
528     );
529
530     my $booking = $builder->build_object(
531         {
532             class => 'Koha::Bookings',
533             value => { biblio_id => $biblio->biblionumber }
534         }
535     );
536
537     my $bookings = $biblio->bookings->unblessed;
538
539     is_deeply(
540         $biblio->bookings->unblessed,
541         [ $booking->unblessed ],
542         '->bookings returns the related Koha::Booking objects'
543     );
544
545     $schema->storage->txn_rollback;
546 };
547
548 subtest 'merge of records' => sub {
549     plan tests => 3;
550     $schema->storage->txn_begin;
551
552     #Three biblio
553     my $biblio1 = $builder->build_sample_biblio( { title => 'Title number 1' } );
554     my $biblio2 = $builder->build_sample_biblio( { title => 'Title number 2' } );
555     my $biblio3 = $builder->build_sample_biblio( { title => 'Title number 3' } );
556
557     # One items each
558     my $item1_1 = $builder->build_sample_item( { biblionumber => $biblio1->biblionumber, barcode => 'bar11' } );
559     my $item2_1 = $builder->build_sample_item( { biblionumber => $biblio2->biblionumber, barcode => 'bar22' } );
560     my $item3_1 = $builder->build_sample_item( { biblionumber => $biblio3->biblionumber, barcode => 'bar33' } );
561     my $results = '';
562     my @to_merge = ( $biblio2->biblionumber, $biblio3->biblionumber );
563
564     my $pre_merged_rs = Koha::Biblios->search(
565         { biblionumber => [ $biblio1->biblionumber, $biblio2->biblionumber, $biblio3->biblionumber ] } );
566     is( $pre_merged_rs->count, 3, '3 biblios exist' );
567
568     eval { $results = $biblio1->merge_with( \@to_merge ); };
569     if ($@) {
570         is( 0, 1, "Not working. Error: " . $@ );
571     } else {
572         my $items = $biblio1->items;
573         is( $items->count, 3, "After merge we have 3 items on first record" );
574
575         my $merged_rs = Koha::Biblios->search(
576             { biblionumber => [ $biblio1->biblionumber, $biblio2->biblionumber, $biblio3->biblionumber ] } );
577         is( $merged_rs->count, 1, 'only 1 biblio left, the merged ones are gone' );
578     }
579
580     $schema->storage->txn_rollback;
581 };
582
583 subtest 'suggestions() tests' => sub {
584
585     plan tests => 3;
586
587     $schema->storage->txn_begin;
588
589     my $biblio     = $builder->build_sample_biblio();
590
591     is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
592
593     is_deeply(
594         $biblio->suggestions->unblessed,
595         [],
596         '->suggestions returns an empty Koha::Suggestions resultset'
597     );
598
599     my $suggestion = $builder->build_object(
600         {
601             class => 'Koha::Suggestions',
602             value => { biblionumber => $biblio->biblionumber }
603         }
604     );
605
606     my $suggestions = $biblio->suggestions->unblessed;
607
608     is_deeply(
609         $biblio->suggestions->unblessed,
610         [ $suggestion->unblessed ],
611         '->suggestions returns the related Koha::Suggestion objects'
612     );
613
614     $schema->storage->txn_rollback;
615 };
616
617 subtest 'get_marc_components() tests' => sub {
618
619     plan tests => 5;
620
621     $schema->storage->txn_begin;
622
623     my ($host_bibnum) = C4::Biblio::AddBiblio(host_record(), '');
624     my $host_biblio = Koha::Biblios->find($host_bibnum);
625     t::lib::Mocks::mock_preference( 'SearchEngine', 'Zebra' );
626     my $search_mod = Test::MockModule->new( 'Koha::SearchEngine::Zebra::Search' );
627     $search_mod->mock( 'search_compat', \&search_component_record2 );
628
629     my $components = $host_biblio->get_marc_components;
630     is( ref($components), 'ARRAY', 'Return type is correct' );
631
632     is_deeply(
633         $components,
634         [],
635         '->get_marc_components returns an empty ARRAY'
636     );
637
638     $search_mod->unmock( 'search_compat');
639     $search_mod->mock( 'search_compat', \&search_component_record1 );
640     my $component_record = component_record1()->as_xml();
641
642     is_deeply(
643         $host_biblio->get_marc_components,
644         [$component_record],
645         '->get_marc_components returns the related component part record'
646     );
647     $search_mod->unmock( 'search_compat');
648
649     $search_mod->mock( 'search_compat',
650         sub { Koha::Exception->throw("error searching analytics") }
651     );
652     warning_like { $components = $host_biblio->get_marc_components }
653         qr{Warning from search_compat: .* 'error searching analytics'};
654
655     is_deeply(
656         $host_biblio->object_messages,
657         [
658             {
659                 type    => 'error',
660                 message => 'component_search',
661                 payload => "Exception 'Koha::Exception' thrown 'error searching analytics'\n"
662             }
663         ]
664     );
665     $search_mod->unmock( 'search_compat');
666
667     $schema->storage->txn_rollback;
668 };
669
670 subtest 'get_components_query' => sub {
671     plan tests => 12;
672
673     my $biblio = $builder->build_sample_biblio();
674     my $biblionumber = $biblio->biblionumber;
675     my $record = $biblio->metadata->record;
676
677     foreach my $engine ('Zebra','Elasticsearch'){
678         t::lib::Mocks::mock_preference( 'SearchEngine', $engine );
679
680         t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
681         t::lib::Mocks::mock_preference( 'ComponentSortField', 'author' );
682         t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'za' );
683         my ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
684         is($comp_query_str, 'Host-item:("Some boring read")', "$engine: UseControlNumber disabled");
685         is($comp_sort, "author_za", "$engine: UseControlNumber disabled sort is correct");
686
687         t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
688         t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'az' );
689         my $marc_001_field = MARC::Field->new('001', $biblionumber);
690         $record->append_fields($marc_001_field);
691         C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
692         $biblio = Koha::Biblios->find( $biblio->biblionumber);
693
694         ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
695         is($comp_query_str, "(rcn:\"$biblionumber\" AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled without MarcOrgCode");
696         is($comp_sort, "author_az", "$engine: UseControlNumber enabled without MarcOrgCode sort is correct");
697
698         my $marc_003_field = MARC::Field->new('003', 'OSt');
699         $record->append_fields($marc_003_field);
700         C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
701         $biblio = Koha::Biblios->find( $biblio->biblionumber);
702
703         t::lib::Mocks::mock_preference( 'ComponentSortField', 'title' );
704         t::lib::Mocks::mock_preference( 'ComponentSortOrder', 'asc' );
705         ( $comp_query, $comp_query_str, $comp_sort ) = $biblio->get_components_query;
706         is($comp_query_str, "(((rcn:\"$biblionumber\" AND cni:\"OSt\") OR rcn:\"OSt $biblionumber\") AND (bib-level:a OR bib-level:b))", "$engine: UseControlNumber enabled with MarcOrgCode");
707         is($comp_sort, "title_asc", "$engine: UseControlNumber enabled with MarcOrgCode sort if correct");
708         $record->delete_field($marc_003_field);
709     }
710 };
711
712 subtest 'get_volumes_query' => sub {
713     plan tests => 3;
714
715     my $biblio       = $builder->build_sample_biblio();
716     my $biblionumber = $biblio->biblionumber;
717     my $record       = $biblio->metadata->record;
718
719     # Ensure our mocked record is captured as a set or monographic series
720     my $ldr = $record->leader();
721     substr( $ldr, 19, 1 ) = 'a';
722     $record->leader($ldr);
723     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
724     $biblio = Koha::Biblios->find( $biblio->biblionumber );
725
726     t::lib::Mocks::mock_preference( 'UseControlNumber', '0' );
727     is(
728         $biblio->get_volumes_query,
729         "(title-series,phr:(\"Some boring read\") OR Host-item,phr:(\"Some boring read\") NOT (bib-level:a OR bib-level:b))",
730         "UseControlNumber disabled"
731     );
732
733     t::lib::Mocks::mock_preference( 'UseControlNumber', '1' );
734     my $marc_001_field = MARC::Field->new( '001', $biblionumber );
735     $record->append_fields($marc_001_field);
736     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
737     $biblio = Koha::Biblios->find( $biblio->biblionumber );
738
739
740     is(
741         $biblio->get_volumes_query, "(rcn:$biblionumber NOT (bib-level:a OR bib-level:b))",
742         "UseControlNumber enabled without MarcOrgCode"
743     );
744
745     my $marc_003_field = MARC::Field->new( '003', 'OSt' );
746     $record->append_fields($marc_003_field);
747     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
748     $biblio = Koha::Biblios->find( $biblio->biblionumber );
749
750     is(
751         $biblio->get_volumes_query,
752         "(((rcn:$biblionumber AND cni:OSt) OR rcn:\"OSt $biblionumber\") NOT (bib-level:a OR bib-level:b))",
753         "UseControlNumber enabled with MarcOrgCode"
754     );
755 };
756
757 subtest 'orders() and active_orders() tests' => sub {
758
759     plan tests => 5;
760
761     $schema->storage->txn_begin;
762
763     my $biblio = $builder->build_sample_biblio();
764
765     my $orders        = $biblio->orders;
766     my $active_orders = $biblio->active_orders;
767
768     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
769     is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
770
771     # Add a couple orders
772     foreach (1..2) {
773         $builder->build_object(
774             {
775                 class => 'Koha::Acquisition::Orders',
776                 value => {
777                     biblionumber => $biblio->biblionumber,
778                     datecancellationprinted => '2019-12-31'
779                 }
780             }
781         );
782     }
783
784     $builder->build_object(
785         {
786             class => 'Koha::Acquisition::Orders',
787             value => {
788                 biblionumber => $biblio->biblionumber,
789                 datecancellationprinted => undef
790             }
791         }
792     );
793
794     $orders = $biblio->orders;
795     $active_orders = $biblio->active_orders;
796
797     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
798     is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
799     is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
800
801     $schema->storage->txn_rollback;
802 };
803
804 subtest 'tickets() tests' => sub {
805
806     plan tests => 4;
807
808     $schema->storage->txn_begin;
809
810     my $biblio = $builder->build_sample_biblio();
811     my $tickets = $biblio->tickets;
812     is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
813     is( $tickets->count, 0, 'Koha::Biblio->tickets should return a count of 0 when there are no related tickets' );
814
815     # Add two tickets
816     foreach (1..2) {
817         $builder->build_object(
818             {
819                 class => 'Koha::Tickets',
820                 value => { biblio_id => $biblio->biblionumber }
821             }
822         );
823     }
824
825     $tickets = $biblio->tickets;
826     is( ref($tickets), 'Koha::Tickets', 'Koha::Biblio->tickets should return a Koha::Tickets object' );
827     is( $tickets->count, 2, 'Koha::Biblio->tickets should return the correct number of tickets' );
828
829     $schema->storage->txn_rollback;
830 };
831
832 subtest 'subscriptions() tests' => sub {
833
834     plan tests => 4;
835
836     $schema->storage->txn_begin;
837
838     my $biblio = $builder->build_sample_biblio;
839
840     my $subscriptions = $biblio->subscriptions;
841     is( ref($subscriptions), 'Koha::Subscriptions',
842         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
843     );
844     is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
845
846     # Add two subscriptions
847     foreach (1..2) {
848         $builder->build_object(
849             {
850                 class => 'Koha::Subscriptions',
851                 value => { biblionumber => $biblio->biblionumber }
852             }
853         );
854     }
855
856     $subscriptions = $biblio->subscriptions;
857     is( ref($subscriptions), 'Koha::Subscriptions',
858         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
859     );
860     is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
861
862     $schema->storage->txn_rollback;
863 };
864
865 subtest 'get_marc_notes() MARC21 tests' => sub {
866     plan tests => 14;
867
868     $schema->storage->txn_begin;
869
870     t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
871
872     my $av = $builder->build_object( { class => 'Koha::AuthorisedValues' } );
873
874     my $biblio = $builder->build_sample_biblio;
875     my $record = $biblio->metadata->record;
876     $record->append_fields(
877         MARC::Field->new( '500', '', '', a => 'Note1' ),
878         MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
879         MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
880         MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
881         MARC::Field->new( '544', '', '', a => 'Note5' ),
882         MARC::Field->new( '590', '', '', a => $av->authorised_value ),
883         MARC::Field->new( '545', '', '', a => 'Invisible on OPAC' ),
884     );
885
886     my $mss = Koha::MarcSubfieldStructures->find({tagfield => "590", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
887     $mss->update({ authorised_value => $av->category });
888
889     $mss = Koha::MarcSubfieldStructures->find({tagfield => "545", tagsubfield => "a", frameworkcode => $biblio->frameworkcode });
890     $mss->update({ hidden => 1 });
891
892     my $cache = Koha::Caches->get_instance;
893     $cache->clear_from_cache("MarcStructure-0-");
894     $cache->clear_from_cache("MarcStructure-1-");
895     $cache->clear_from_cache("MarcSubfieldStructure-");
896     $cache->clear_from_cache("MarcCodedFields-");
897
898     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
899     $biblio = Koha::Biblios->find( $biblio->biblionumber);
900
901     my $notes = $biblio->get_marc_notes;
902     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
903     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
904     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
905     is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Note shows if not opac (Hidden by Indicator)" );
906     is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
907     is( $notes->[5]->{marcnote}, $av->lib, 'Authorised value is correctly parsed to show description rather than code' );
908     is( $notes->[6]->{marcnote}, 'Invisible on OPAC', 'Note shows if not opac (Hidden by framework)' );
909     is( @$notes, 7, 'No more notes' );
910     $notes = $biblio->get_marc_notes({ opac => 1 });
911     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
912     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
913     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
914     is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
915     is( $notes->[4]->{marcnote}, $av->lib_opac, 'Authorised value is correctly parsed for OPAC to show description rather than code' );
916     is( @$notes, 5, 'No more notes' );
917
918     $cache->clear_from_cache("MarcStructure-0-");
919     $cache->clear_from_cache("MarcStructure-1-");
920     $cache->clear_from_cache("MarcSubfieldStructure-");
921     $cache->clear_from_cache("MarcCodedFields-");
922
923     $schema->storage->txn_rollback;
924 };
925
926 subtest 'get_marc_notes() UNIMARC tests' => sub {
927     plan tests => 3;
928
929     $schema->storage->txn_begin;
930
931     t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
932     t::lib::Mocks::mock_preference( 'marcflavour', 'UNIMARC' );
933
934     my $biblio = $builder->build_sample_biblio;
935     my $record = $biblio->metadata->record;
936     $record->append_fields(
937         MARC::Field->new( '300', '', '', a => 'Note1' ),
938         MARC::Field->new( '300', '', '', a => 'Note2' ),
939         MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
940     );
941     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
942     $biblio = Koha::Biblios->find( $biblio->biblionumber);
943     my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
944     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
945     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
946     is( @$notes, 2, 'No more notes' );
947
948     t::lib::Mocks::mock_preference( 'marcflavour', 'MARC21' );
949     $schema->storage->txn_rollback;
950 };
951
952 subtest 'host_items() tests' => sub {
953     plan tests => 7;
954
955     $schema->storage->txn_begin;
956
957     my $biblio = $builder->build_sample_biblio( { frameworkcode => '' } );
958
959     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 1 );
960     my $host_items = $biblio->host_items;
961     is( ref($host_items),   'Koha::Items' );
962     is( $host_items->count, 0 );
963
964     my $item_1 =
965       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
966     my $host_item_1 = $builder->build_sample_item;
967     my $host_item_2 = $builder->build_sample_item;
968
969     my $record = $biblio->metadata->record;
970     $record->append_fields(
971         MARC::Field->new(
972             '773', '', '',
973             9 => $host_item_1->itemnumber,
974             9 => $host_item_2->itemnumber
975         ),
976     );
977     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
978     $biblio = $biblio->get_from_storage;
979     $host_items = $biblio->host_items;
980     is( $host_items->count, 2 );
981     is_deeply( [ $host_items->get_column('itemnumber') ],
982         [ $host_item_1->itemnumber, $host_item_2->itemnumber ] );
983
984     t::lib::Mocks::mock_preference( 'EasyAnalyticalRecords', 0 );
985     $host_items = $biblio->host_items;
986     is( ref($host_items),   'Koha::Items' );
987     is( $host_items->count, 0 );
988
989     subtest 'test host_items param in items()' => sub {
990         plan tests => 4;
991
992         my $items = $biblio->items;
993         is( $items->count, 1, "Without host_items param we only get the items on the biblio");
994         $items = $biblio->items({ host_items => 1 });
995         is( $items->count, 3, "With param host_items we get the biblio items plus analytics");
996         is( ref($items), 'Koha::Items', "We correctly get an Items object");
997         is_deeply( [ $items->get_column('itemnumber') ],
998             [ $item_1->itemnumber, $host_item_1->itemnumber, $host_item_2->itemnumber ] );
999     };
1000
1001     $schema->storage->txn_rollback;
1002 };
1003
1004 subtest 'article_requests() tests' => sub {
1005
1006     plan tests => 3;
1007
1008     $schema->storage->txn_begin;
1009
1010     my $item   = $builder->build_sample_item;
1011     my $biblio = $item->biblio;
1012
1013     my $article_requests = $biblio->article_requests;
1014     is( ref($article_requests), 'Koha::ArticleRequests',
1015         'In scalar context, type is correct' );
1016     is( $article_requests->count, 0, 'No article requests' );
1017
1018     foreach my $i ( 0 .. 3 ) {
1019
1020         my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1021
1022         Koha::ArticleRequest->new(
1023             {
1024                 borrowernumber => $patron->id,
1025                 biblionumber   => $biblio->id,
1026                 itemnumber     => $item->id,
1027                 title          => $biblio->title,
1028             }
1029         )->request;
1030     }
1031
1032     $article_requests = $biblio->article_requests;
1033     is( $article_requests->count, 4, '4 article requests' );
1034
1035     $schema->storage->txn_rollback;
1036 };
1037
1038 subtest 'current_checkouts() and old_checkouts() tests' => sub {
1039
1040     plan tests => 4;
1041
1042     $schema->storage->txn_begin;
1043
1044     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1045
1046     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
1047     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
1048
1049     my $item_1 = $builder->build_sample_item;
1050     my $item_2 = $builder->build_sample_item({ biblionumber => $item_1->biblionumber });
1051
1052     t::lib::Mocks::mock_userenv({ branchcode => $library->id });
1053
1054     AddIssue( $patron_1, $item_1->barcode );
1055     AddIssue( $patron_1, $item_2->barcode );
1056
1057     AddReturn( $item_1->barcode );
1058     AddIssue( $patron_2, $item_1->barcode );
1059
1060     my $biblio = $item_1->biblio;
1061     my $current_checkouts = $biblio->current_checkouts;
1062     my $old_checkouts = $biblio->old_checkouts;
1063
1064     is( ref($current_checkouts), 'Koha::Checkouts', 'Type is correct' );
1065     is( ref($old_checkouts), 'Koha::Old::Checkouts', 'Type is correct' );
1066
1067     is( $current_checkouts->count, 2, 'Count is correct for current checkouts' );
1068     is( $old_checkouts->count, 1, 'Count is correct for old checkouts' );
1069
1070     $schema->storage->txn_rollback;
1071 };
1072
1073 subtest 'get_marc_contributors() tests' => sub {
1074
1075     plan tests => 2;
1076
1077     $schema->storage->txn_begin;
1078
1079     my $biblio = $builder->build_sample_biblio({ author => 'Main author' });
1080     my $record = $biblio->metadata->record;
1081
1082     # add author information
1083     my $field = MARC::Field->new('700','1','','a' => 'Jefferson, Thomas');
1084     $record->append_fields($field);
1085     $field = MARC::Field->new('701','1','','d' => 'Secondary author 2');
1086     $record->append_fields($field);
1087
1088     # get record
1089     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
1090     $biblio = Koha::Biblios->find( $biblio->biblionumber );
1091
1092     is( @{$biblio->get_marc_authors}, 3, 'get_marc_authors retrieves correct number of author subfields' );
1093     is( @{$biblio->get_marc_contributors}, 2, 'get_marc_contributors retrieves correct number of author subfields' );
1094     $schema->storage->txn_rollback;
1095 };
1096
1097 subtest 'Recalls tests' => sub {
1098
1099     plan tests => 13;
1100
1101     $schema->storage->txn_begin;
1102
1103     my $item1 = $builder->build_sample_item;
1104     my $biblio = $item1->biblio;
1105     my $branchcode = $item1->holdingbranch;
1106     my $patron1 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1107     my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1108     my $patron3 = $builder->build_object({ class => 'Koha::Patrons', value => { branchcode => $branchcode } });
1109     my $item2 = $builder->build_object({ class => 'Koha::Items', value => { holdingbranch => $branchcode, homebranch => $branchcode, biblionumber => $biblio->biblionumber, itype => $item1->effective_itemtype } });
1110     t::lib::Mocks::mock_userenv({ patron => $patron1 });
1111
1112     my $recall1 = Koha::Recall->new(
1113         {   patron_id         => $patron1->borrowernumber,
1114             created_date      => \'NOW()',
1115             biblio_id         => $biblio->biblionumber,
1116             pickup_library_id => $branchcode,
1117             item_id           => $item1->itemnumber,
1118             expiration_date   => undef,
1119             item_level        => 1
1120         }
1121     )->store;
1122     my $recall2 = Koha::Recall->new(
1123         {   patron_id         => $patron2->borrowernumber,
1124             created_date      => \'NOW()',
1125             biblio_id         => $biblio->biblionumber,
1126             pickup_library_id => $branchcode,
1127             item_id           => undef,
1128             expiration_date   => undef,
1129             item_level        => 0
1130         }
1131     )->store;
1132     my $recall3 = Koha::Recall->new(
1133         {   patron_id         => $patron3->borrowernumber,
1134             created_date      => \'NOW()',
1135             biblio_id         => $biblio->biblionumber,
1136             pickup_library_id => $branchcode,
1137             item_id           => $item1->itemnumber,
1138             expiration_date   => undef,
1139             item_level        => 1
1140         }
1141     )->store;
1142
1143     my $recalls = $biblio->recalls;
1144     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
1145
1146     $recall1->set_cancelled;
1147     $recall2->set_expired({ interface => 'COMMANDLINE' });
1148
1149     is( $recalls->count, 3, 'Correctly get number of recalls for biblio' );
1150     is( $recalls->filter_by_current->count, 1, 'Correctly get number of active recalls for biblio' );
1151
1152     t::lib::Mocks::mock_preference('UseRecalls', 0);
1153     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with UseRecalls disabled" );
1154
1155     t::lib::Mocks::mock_preference("UseRecalls", 1);
1156     $item1->update({ notforloan => 1 });
1157     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall with no available items" );
1158
1159     $item1->update({ notforloan => 0 });
1160     Koha::CirculationRules->set_rules({
1161         branchcode => $branchcode,
1162         categorycode => $patron1->categorycode,
1163         itemtype => $item1->effective_itemtype,
1164         rules => {
1165             recalls_allowed => 0,
1166             recalls_per_record => 1,
1167             on_shelf_recalls => 'all',
1168         },
1169     });
1170     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if recalls_allowed = 0" );
1171
1172     Koha::CirculationRules->set_rules({
1173         branchcode => $branchcode,
1174         categorycode => $patron1->categorycode,
1175         itemtype => $item1->effective_itemtype,
1176         rules => {
1177             recalls_allowed => 1,
1178             recalls_per_record => 1,
1179             on_shelf_recalls => 'all',
1180         },
1181     });
1182     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_allowed" );
1183     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has more existing recall(s) than recalls_per_record" );
1184
1185     $recall1->set_cancelled;
1186     C4::Circulation::AddIssue( $patron1, $item2->barcode );
1187     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if patron has already checked out an item attached to this biblio" );
1188
1189     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if on_shelf_recalls = all and items are still available" );
1190
1191     Koha::CirculationRules->set_rules({
1192         branchcode => $branchcode,
1193         categorycode => $patron1->categorycode,
1194         itemtype => $item1->effective_itemtype,
1195         rules => {
1196             recalls_allowed => 1,
1197             recalls_per_record => 1,
1198             on_shelf_recalls => 'any',
1199         },
1200     });
1201     C4::Circulation::AddReturn( $item2->barcode, $branchcode );
1202     is( $biblio->can_be_recalled({ patron => $patron1 }), 0, "Can't recall if no items are checked out" );
1203
1204     $recall2->set_cancelled;
1205     C4::Circulation::AddIssue( $patron2, $item2->barcode );
1206     C4::Circulation::AddIssue( $patron2, $item1->barcode );
1207     is( $biblio->can_be_recalled({ patron => $patron1 }), 2, "Can recall two items" );
1208
1209     $item1->update({ withdrawn => 1 });
1210     is( $biblio->can_be_recalled({ patron => $patron1 }), 1, "Can recall one item" );
1211
1212     $schema->storage->txn_rollback;
1213 };
1214
1215 subtest 'ill_requests() tests' => sub {
1216
1217     plan tests => 3;
1218
1219     $schema->storage->txn_begin;
1220
1221     my $biblio = $builder->build_sample_biblio;
1222
1223     my $rs = $biblio->ill_requests;
1224     is( ref($rs), 'Koha::Illrequests' );
1225     is( $rs->count, 0, 'No linked requests' );
1226
1227     foreach ( 1..10 ) {
1228         $builder->build_object(
1229             {
1230                 class => 'Koha::Illrequests',
1231                 value => { biblio_id => $biblio->id }
1232             }
1233         );
1234     }
1235
1236     is( $biblio->ill_requests->count, 10, 'Linked requests are present' );
1237
1238     $schema->storage->txn_rollback;
1239 };
1240
1241 subtest 'item_groups() tests' => sub {
1242
1243     plan tests => 6;
1244
1245     $schema->storage->txn_begin;
1246
1247     my $biblio = $builder->build_sample_biblio();
1248
1249     my @item_groups = $biblio->item_groups->as_list;
1250     is( scalar(@item_groups), 0, 'Got zero item groups');
1251
1252     my $item_group_1 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1253
1254     @item_groups = $biblio->item_groups->as_list;
1255     is( scalar(@item_groups), 1, 'Got one item group');
1256     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group');
1257
1258     my $item_group_2 = Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
1259
1260     @item_groups = $biblio->item_groups->as_list;
1261     is( scalar(@item_groups), 2, 'Got two item groups');
1262     is( $item_groups[0]->id, $item_group_1->id, 'Got correct item group 1');
1263     is( $item_groups[1]->id, $item_group_2->id, 'Got correct item group 2');
1264
1265     $schema->storage->txn_rollback;
1266 };
1267
1268 subtest 'normalized_isbn' => sub {
1269     plan tests => 1;
1270
1271     # We will move the tests from GetNormalizedISBN here when it will get replaced
1272     my $biblio = $builder->build_sample_biblio();
1273     $biblio->biblioitem->set( { isbn => '9781250067128 | 125006712X' } )->store;
1274     is(
1275         $biblio->normalized_isbn, C4::Koha::GetNormalizedISBN( $biblio->biblioitem->isbn ),
1276         'normalized_isbn is a wrapper around C4::Koha::GetNormalizedISBN'
1277     );
1278 };
1279
1280 subtest 'normalized_upc' => sub {
1281     plan tests => 1;
1282
1283     # We will move the tests from GetNormalizedUPC here when it will get replaced
1284     # Note that only a single test exist and it's not really meaningful...
1285     my $biblio = $builder->build_sample_biblio();
1286     is(
1287         $biblio->normalized_upc, C4::Koha::GetNormalizedUPC( $biblio->metadata->record ),
1288         'normalized_upc is a wrapper around C4::Koha::GetNormalizedUPC'
1289     );
1290 };
1291
1292 subtest 'normalized_oclc' => sub {
1293     plan tests => 1;
1294
1295     # We will move the tests from GetNormalizedOCLC here when it will get replaced
1296     # Note that only a single test exist and it's not really meaningful...
1297     my $biblio = $builder->build_sample_biblio();
1298     is(
1299         $biblio->normalized_oclc, C4::Koha::GetNormalizedOCLCNumber( $biblio->metadata->record ),
1300         'normalized_oclc is a wrapper around C4::Koha::GetNormalizedOCLCNumber'
1301     );
1302 };
1303
1304 subtest 'ratings' => sub {
1305     plan tests => 1;
1306     # See t/db_dependent/Koha/Ratings.t
1307     ok(1);
1308 };
1309
1310 subtest 'opac_summary_html' => sub {
1311
1312     plan tests => 2;
1313
1314     my $author = 'my author';
1315     my $title  = 'my title';
1316     my $isbn   = '9781250067128 | 125006712X';
1317     my $biblio = $builder->build_sample_biblio( { author => $author, title => $title } );
1318     $biblio->biblioitem->set( { isbn => '9781250067128 | 125006712X' } )->store;
1319
1320     t::lib::Mocks::mock_preference( 'OPACMySummaryHTML', '' );
1321     is( $biblio->opac_summary_html, '', 'opac_summary_html returns empty string if pref is off' );
1322
1323     t::lib::Mocks::mock_preference(
1324         'OPACMySummaryHTML',
1325         'Replace {AUTHOR}, {TITLE}, {ISBN} AND {BIBLIONUMBER} please'
1326     );
1327     is(
1328         $biblio->opac_summary_html,
1329         sprintf( 'Replace %s, %s, %s AND %s please', $author, $title, $biblio->normalized_isbn, $biblio->biblionumber ),
1330         'opac_summary_html replaces the different patterns'
1331     );
1332 };
1333
1334 sub component_record1 {
1335     my $marc = MARC::Record->new;
1336     $marc->append_fields(
1337         MARC::Field->new( '001', '3456' ),
1338         MARC::Field->new( '245', '', '', a => 'Some title 1' ),
1339         MARC::Field->new( '773', '', '', w => '(FIRST)1234' ),
1340     );
1341     return $marc;
1342 }
1343 sub search_component_record1 {
1344     my @results = ( component_record1()->as_xml() );
1345     return ( undef, { biblioserver => { RECORDS => \@results, hits => 1 } }, 1 );
1346 }
1347
1348 sub search_component_record2 {
1349     my @results;
1350     return ( undef, { biblioserver => { RECORDS => \@results, hits => 0 } }, 0 );
1351 }
1352
1353 sub host_record {
1354     my $marc = MARC::Record->new;
1355     $marc->append_fields(
1356         MARC::Field->new( '001', '1234' ),
1357         MARC::Field->new( '003', 'FIRST' ),
1358         MARC::Field->new( '245', '', '', a => 'Some title' ),
1359     );
1360     return $marc;
1361 }
1362
1363 subtest 'check_booking tests' => sub {
1364     plan tests => 4;
1365
1366     $schema->storage->txn_begin;
1367
1368     my $biblio = $builder->build_sample_biblio();
1369     my @items;
1370     for ( 0 .. 2 ) {
1371         my $item = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, bookable => 1 } );
1372         push @items, $item;
1373     }
1374
1375     my $can_book = $biblio->check_booking(
1376         {
1377             start_date => dt_from_string(),
1378             end_date   => dt_from_string()->add( days => 7 )
1379         }
1380     );
1381
1382     is(
1383         $can_book, 1,
1384         "True returned from Koha::Biblio->check_booking if there are no bookings"
1385     );
1386
1387     my $start_1 = dt_from_string()->subtract( days => 7 );
1388     my $end_1   = dt_from_string()->subtract( days => 1 );
1389     my $start_2 = dt_from_string();
1390     my $end_2   = dt_from_string()->add( days => 7 );
1391
1392     # Past bookings
1393     my @bookings;
1394     for my $item (@items) {
1395
1396         my $booking = $builder->build_object(
1397             {
1398                 class => 'Koha::Bookings',
1399                 value => {
1400                     biblio_id  => $biblio->biblionumber,
1401                     item_id    => $item->itemnumber,
1402                     start_date => $start_1,
1403                     end_date   => $end_1
1404                 }
1405             }
1406         );
1407         push @bookings, $booking;
1408     }
1409
1410     $can_book = $biblio->check_booking(
1411         {
1412             start_date => dt_from_string(),
1413             end_date   => dt_from_string()->add( days => 7 ),
1414         }
1415     );
1416
1417     is(
1418         $can_book,
1419         1,
1420         "Koha::Biblio->check_booking returns true when we all existing bookings are in the past"
1421     );
1422
1423     # Current bookings
1424     my @current_bookings;
1425     for my $item (@items) {
1426         my $booking = $builder->build_object(
1427             {
1428                 class => 'Koha::Bookings',
1429                 value => {
1430                     biblio_id  => $biblio->biblionumber,
1431                     item_id    => $item->itemnumber,
1432                     start_date => $start_2,
1433                     end_date   => $end_2
1434                 }
1435             }
1436         );
1437         push @current_bookings, $booking;
1438     }
1439
1440     $can_book = $biblio->check_booking(
1441         {
1442             start_date => dt_from_string(),
1443             end_date   => dt_from_string()->add( days => 7 ),
1444         }
1445     );
1446     is(
1447         $can_book,
1448         0,
1449         "Koha::Biblio->check_booking returns false if the booking would conflict with existing bookings"
1450     );
1451
1452     $can_book = $biblio->check_booking(
1453         {
1454             start_date => dt_from_string(),
1455             end_date   => dt_from_string()->add( days => 7 ),
1456             booking_id => $current_bookings[0]->booking_id
1457         }
1458     );
1459     is(
1460         $can_book,
1461         1,
1462         "Koha::Biblio->check_booking returns true if we pass the booking_id of one of the bookings that we would conflict with"
1463     );
1464
1465     $schema->storage->txn_rollback;
1466 };