Bug 22440: (follow-up) Nothing is forever
[koha.git] / t / db_dependent / Koha / Hold.t
1 #!/usr/bin/perl
2
3 # Copyright 2020 Koha Development team
4 #
5 # This file is part of Koha
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Test::More tests => 11;
23
24 use Test::Exception;
25 use Test::MockModule;
26
27 use t::lib::Mocks;
28 use t::lib::TestBuilder;
29 use t::lib::Mocks;
30
31 use Koha::ActionLogs;
32 use Koha::DateUtils qw(dt_from_string);
33 use Koha::Holds;
34 use Koha::Libraries;
35 use Koha::Old::Holds;
36
37 my $schema  = Koha::Database->new->schema;
38 my $builder = t::lib::TestBuilder->new;
39
40 subtest 'store() tests' => sub {
41     plan tests => 2;
42
43     $schema->storage->txn_begin;
44
45     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
46     my $item   = $builder->build_sample_item;
47     throws_ok {
48         Koha::Hold->new(
49             {
50                 borrowernumber => $patron->borrowernumber,
51                 biblionumber   => $item->biblionumber,
52                 priority       => 1,
53                 itemnumber     => $item->itemnumber,
54             }
55         )->store
56     }
57     'Koha::Exceptions::Hold::MissingPickupLocation',
58       'Exception thrown because branchcode was not passed';
59
60     my $hold = $builder->build_object( { class => 'Koha::Holds' } );
61     throws_ok {
62         $hold->branchcode(undef)->store;
63     }
64     'Koha::Exceptions::Hold::MissingPickupLocation',
65       'Exception thrown if one tries to set branchcode to null';
66
67     $schema->storage->txn_rollback;
68 };
69
70 subtest 'fill() tests' => sub {
71
72     plan tests => 13;
73
74     $schema->storage->txn_begin;
75
76     my $fee = 15;
77
78     my $category = $builder->build_object(
79         {
80             class => 'Koha::Patron::Categories',
81             value => { reservefee => $fee }
82         }
83     );
84     my $patron = $builder->build_object(
85         {
86             class => 'Koha::Patrons',
87             value => { categorycode => $category->id }
88         }
89     );
90     my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
91
92     my $title  = 'Do what you want';
93     my $biblio = $builder->build_sample_biblio( { title => $title } );
94     my $item   = $builder->build_sample_item( { biblionumber => $biblio->id } );
95     my $hold   = $builder->build_object(
96         {
97             class => 'Koha::Holds',
98             value => {
99                 biblionumber   => $biblio->id,
100                 borrowernumber => $patron->id,
101                 itemnumber     => $item->id,
102                 priority       => 10,
103             }
104         }
105     );
106
107     t::lib::Mocks::mock_preference( 'HoldFeeMode', 'any_time_is_collected' );
108     t::lib::Mocks::mock_preference( 'HoldsLog',    1 );
109     t::lib::Mocks::mock_userenv(
110         { patron => $manager, branchcode => $manager->branchcode } );
111
112     my $interface = 'api';
113     C4::Context->interface($interface);
114
115     my $ret = $hold->fill;
116
117     is( ref($ret), 'Koha::Hold', '->fill returns the object type' );
118     is( $ret->id, $hold->id, '->fill returns the object' );
119
120     is( Koha::Holds->find($hold->id), undef, 'Hold no longer current' );
121     my $old_hold = Koha::Old::Holds->find( $hold->id );
122
123     is( $old_hold->id, $hold->id, 'reserve_id retained' );
124     is( $old_hold->priority, 0, 'priority set to 0' );
125     is( $old_hold->found, 'F', 'found set to F' );
126
127     subtest 'fee applied tests' => sub {
128
129         plan tests => 9;
130
131         my $account = $patron->account;
132         is( $account->balance, $fee, 'Charge applied correctly' );
133
134         my $debits = $account->outstanding_debits;
135         is( $debits->count, 1, 'Only one fee charged' );
136
137         my $fee_debit = $debits->next;
138         is( $fee_debit->amount * 1, $fee, 'Fee amount stored correctly' );
139         is( $fee_debit->description, $title,
140             'Fee description stored correctly' );
141         is( $fee_debit->manager_id, $manager->id,
142             'Fee manager_id stored correctly' );
143         is( $fee_debit->branchcode, $manager->branchcode,
144             'Fee branchcode stored correctly' );
145         is( $fee_debit->interface, $interface,
146             'Fee interface stored correctly' );
147         is( $fee_debit->debit_type_code,
148             'RESERVE', 'Fee debit_type_code stored correctly' );
149         is( $fee_debit->itemnumber, $item->id,
150             'Fee itemnumber stored correctly' );
151     };
152
153     my $logs = Koha::ActionLogs->search(
154         {
155             action => 'FILL',
156             module => 'HOLDS',
157             object => $hold->id
158         }
159     );
160
161     is( $logs->count, 1, '1 log line added' );
162
163     # Set HoldFeeMode to something other than any_time_is_collected
164     t::lib::Mocks::mock_preference( 'HoldFeeMode', 'not_always' );
165     # Disable logging
166     t::lib::Mocks::mock_preference( 'HoldsLog',    0 );
167
168     $hold = $builder->build_object(
169         {
170             class => 'Koha::Holds',
171             value => {
172                 biblionumber   => $biblio->id,
173                 borrowernumber => $patron->id,
174                 itemnumber     => $item->id,
175                 priority       => 10,
176             }
177         }
178     );
179
180     $hold->fill;
181
182     my $account = $patron->account;
183     is( $account->balance, $fee, 'No new charge applied' );
184
185     my $debits = $account->outstanding_debits;
186     is( $debits->count, 1, 'Only one fee charged, because of HoldFeeMode' );
187
188     $logs = Koha::ActionLogs->search(
189         {
190             action => 'FILL',
191             module => 'HOLDS',
192             object => $hold->id
193         }
194     );
195
196     is( $logs->count, 0, 'HoldsLog disabled, no logs added' );
197
198     subtest 'anonymization behavior tests' => sub {
199
200         plan tests => 5;
201
202         # reduce the tests noise
203         t::lib::Mocks::mock_preference( 'HoldsLog',    0 );
204         t::lib::Mocks::mock_preference( 'HoldFeeMode', 'not_always' );
205         # unset AnonymousPatron
206         t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
207
208         # 0 == keep forever
209         $patron->privacy(0)->store;
210         my $hold = $builder->build_object(
211             {
212                 class => 'Koha::Holds',
213                 value => { borrowernumber => $patron->id, found => undef }
214             }
215         );
216         $hold->fill();
217         is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
218             $patron->borrowernumber, 'Patron link is kept' );
219
220         # 1 == "default", meaning it is not protected from removal
221         $patron->privacy(1)->store;
222         $hold = $builder->build_object(
223             {
224                 class => 'Koha::Holds',
225                 value => { borrowernumber => $patron->id, found => undef }
226             }
227         );
228         $hold->fill();
229         is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
230             $patron->borrowernumber, 'Patron link is kept' );
231
232         my $anonymous_patron = $builder->build_object({ class => 'Koha::Patrons' });
233         t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
234         # We need anonymous patron set to change patron privacy to never
235         # (2 == delete immediately)
236         # then we can undef for further tests
237         $patron->privacy(2)->store;
238         t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
239         $hold = $builder->build_object(
240             {
241                 class => 'Koha::Holds',
242                 value => { borrowernumber => $patron->id, found => undef }
243             }
244         );
245
246         throws_ok
247             { $hold->fill(); }
248             'Koha::Exception',
249             'AnonymousPatron not set, exception thrown';
250
251         $hold->discard_changes; # refresh from DB
252
253         ok( !$hold->is_found, 'Hold is not filled' );
254
255         t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
256
257         $hold = $builder->build_object(
258             {
259                 class => 'Koha::Holds',
260                 value => { borrowernumber => $patron->id, found => undef }
261             }
262         );
263         $hold->fill();
264         is(
265             Koha::Old::Holds->find( $hold->id )->borrowernumber,
266             $anonymous_patron->id,
267             'Patron link is set to the configured anonymous patron immediately'
268         );
269     };
270
271     subtest 'holds_queue update tests' => sub {
272
273         plan tests => 1;
274
275         my $biblio = $builder->build_sample_biblio;
276
277         my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
278         $mock->mock( 'enqueue', sub {
279             my ( $self, $args ) = @_;
280             is_deeply(
281                 $args->{biblio_ids},
282                 [ $biblio->id ],
283                 '->fill triggers a holds queue update for the related biblio'
284             );
285         } );
286
287         t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
288
289         $builder->build_object(
290             {
291                 class => 'Koha::Holds',
292                 value => {
293                     biblionumber   => $biblio->id,
294                 }
295             }
296         )->fill;
297
298         t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
299         # this call shouldn't add a new test
300         $builder->build_object(
301             {
302                 class => 'Koha::Holds',
303                 value => {
304                     biblionumber   => $biblio->id,
305                 }
306             }
307         )->fill;
308     };
309
310     $schema->storage->txn_rollback;
311 };
312
313 subtest 'patron() tests' => sub {
314
315     plan tests => 2;
316
317     $schema->storage->txn_begin;
318
319     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
320     my $hold   = $builder->build_object(
321         {
322             class => 'Koha::Holds',
323             value => {
324                 borrowernumber => $patron->borrowernumber
325             }
326         }
327     );
328
329     my $hold_patron = $hold->patron;
330     is( ref($hold_patron), 'Koha::Patron', 'Right type' );
331     is( $hold_patron->id, $patron->id, 'Right object' );
332
333     $schema->storage->txn_rollback;
334 };
335
336 subtest 'set_pickup_location() tests' => sub {
337
338     plan tests => 11;
339
340     $schema->storage->txn_begin;
341
342     my $mock_biblio = Test::MockModule->new('Koha::Biblio');
343     my $mock_item   = Test::MockModule->new('Koha::Item');
344
345     my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
346     my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
347     my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
348
349     # let's control what Koha::Biblio->pickup_locations returns, for testing
350     $mock_biblio->mock( 'pickup_locations', sub {
351         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
352     });
353     # let's mock what Koha::Item->pickup_locations returns, for testing
354     $mock_item->mock( 'pickup_locations', sub {
355         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
356     });
357
358     my $biblio = $builder->build_sample_biblio;
359     my $item   = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
360
361     # Test biblio-level holds
362     my $biblio_hold = $builder->build_object(
363         {
364             class => "Koha::Holds",
365             value => {
366                 biblionumber => $biblio->biblionumber,
367                 branchcode   => $library_3->branchcode,
368                 itemnumber   => undef,
369             }
370         }
371     );
372
373     throws_ok
374         { $biblio_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
375         'Koha::Exceptions::Hold::InvalidPickupLocation',
376         'Exception thrown on invalid pickup location';
377
378     $biblio_hold->discard_changes;
379     is( $biblio_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
380
381     my $ret = $biblio_hold->set_pickup_location({ library_id => $library_2->id });
382     is( ref($ret), 'Koha::Hold', 'self is returned' );
383
384     $biblio_hold->discard_changes;
385     is( $biblio_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
386
387     # Test item-level holds
388     my $item_hold = $builder->build_object(
389         {
390             class => "Koha::Holds",
391             value => {
392                 biblionumber => $biblio->biblionumber,
393                 branchcode   => $library_3->branchcode,
394                 itemnumber   => $item->itemnumber,
395             }
396         }
397     );
398
399     throws_ok
400         { $item_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
401         'Koha::Exceptions::Hold::InvalidPickupLocation',
402         'Exception thrown on invalid pickup location';
403
404     $item_hold->discard_changes;
405     is( $item_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
406
407     $item_hold->set_pickup_location({ library_id => $library_1->branchcode, force => 1 });
408     $item_hold->discard_changes;
409     is( $item_hold->branchcode, $library_1->branchcode, 'branchcode changed because of \'force\'' );
410
411     $ret = $item_hold->set_pickup_location({ library_id => $library_2->id });
412     is( ref($ret), 'Koha::Hold', 'self is returned' );
413
414     $item_hold->discard_changes;
415     is( $item_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
416
417     throws_ok
418         { $item_hold->set_pickup_location({ library_id => undef }); }
419         'Koha::Exceptions::MissingParameter',
420         'Exception thrown if missing parameter';
421
422     like( "$@", qr/The library_id parameter is mandatory/, 'Exception message is clear' );
423
424     $schema->storage->txn_rollback;
425 };
426
427 subtest 'is_pickup_location_valid() tests' => sub {
428
429     plan tests => 5;
430
431     $schema->storage->txn_begin;
432
433     my $mock_biblio = Test::MockModule->new('Koha::Biblio');
434     my $mock_item   = Test::MockModule->new('Koha::Item');
435
436     my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
437     my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
438     my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
439
440     # let's control what Koha::Biblio->pickup_locations returns, for testing
441     $mock_biblio->mock( 'pickup_locations', sub {
442         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
443     });
444     # let's mock what Koha::Item->pickup_locations returns, for testing
445     $mock_item->mock( 'pickup_locations', sub {
446         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
447     });
448
449     my $biblio = $builder->build_sample_biblio;
450     my $item   = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
451
452     # Test biblio-level holds
453     my $biblio_hold = $builder->build_object(
454         {
455             class => "Koha::Holds",
456             value => {
457                 biblionumber => $biblio->biblionumber,
458                 branchcode   => $library_3->branchcode,
459                 itemnumber   => undef,
460             }
461         }
462     );
463
464     ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
465     ok( $biblio_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid');
466
467     # Test item-level holds
468     my $item_hold = $builder->build_object(
469         {
470             class => "Koha::Holds",
471             value => {
472                 biblionumber => $biblio->biblionumber,
473                 branchcode   => $library_3->branchcode,
474                 itemnumber   => $item->itemnumber,
475             }
476         }
477     );
478
479     ok( !$item_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
480     ok( $item_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid' );
481
482     subtest 'pickup_locations() returning ->empty' => sub {
483
484         plan tests => 2;
485
486         $schema->storage->txn_begin;
487
488         my $library = $builder->build_object({ class => 'Koha::Libraries' });
489
490         my $mock_item = Test::MockModule->new('Koha::Item');
491         $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
492
493         my $mock_biblio = Test::MockModule->new('Koha::Biblio');
494         $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
495
496         my $item   = $builder->build_sample_item();
497         my $biblio = $item->biblio;
498
499         # Test biblio-level holds
500         my $biblio_hold = $builder->build_object(
501             {
502                 class => "Koha::Holds",
503                 value => {
504                     biblionumber => $biblio->biblionumber,
505                     itemnumber   => undef,
506                 }
507             }
508         );
509
510         ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
511
512         # Test item-level holds
513         my $item_hold = $builder->build_object(
514             {
515                 class => "Koha::Holds",
516                 value => {
517                     biblionumber => $biblio->biblionumber,
518                     itemnumber   => $item->itemnumber,
519                 }
520             }
521         );
522
523         ok( !$item_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
524
525         $schema->storage->txn_rollback;
526     };
527
528     $schema->storage->txn_rollback;
529 };
530
531 subtest 'cancel() tests' => sub {
532
533     plan tests => 6;
534
535     $schema->storage->txn_begin;
536
537     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
538
539     # reduce the tests noise
540     t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
541     t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelayCharge',
542         undef );
543
544     t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
545
546     # 0 == keep forever
547     $patron->privacy(0)->store;
548     my $hold = $builder->build_object(
549         {
550             class => 'Koha::Holds',
551             value => { borrowernumber => $patron->id, found => undef }
552         }
553     );
554     $hold->cancel();
555     is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
556         $patron->borrowernumber, 'Patron link is kept' );
557
558     # 1 == "default", meaning it is not protected from removal
559     $patron->privacy(1)->store;
560     $hold = $builder->build_object(
561         {
562             class => 'Koha::Holds',
563             value => { borrowernumber => $patron->id, found => undef }
564         }
565     );
566     $hold->cancel();
567     is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
568         $patron->borrowernumber, 'Patron link is kept' );
569
570     my $anonymous_patron = $builder->build_object({ class => 'Koha::Patrons' });
571     t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
572     # We need anonymous patron set to change patron privacy to never
573     # (2 == delete immediately)
574     # then we can undef for further tests
575     $patron->privacy(2)->store;
576     t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
577     $hold = $builder->build_object(
578         {
579             class => 'Koha::Holds',
580             value => { borrowernumber => $patron->id, found => undef }
581         }
582     );
583     throws_ok
584         { $hold->cancel(); }
585         'Koha::Exception',
586         'AnonymousPatron not set, exception thrown';
587
588     $hold->discard_changes;
589
590     ok( !$hold->is_found, 'Hold is not cancelled' );
591
592     t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
593
594     $hold = $builder->build_object(
595         {
596             class => 'Koha::Holds',
597             value => { borrowernumber => $patron->id, found => undef }
598         }
599     );
600     $hold->cancel();
601     is(
602         Koha::Old::Holds->find( $hold->id )->borrowernumber,
603         $anonymous_patron->id,
604         'Patron link is set to the configured anonymous patron immediately'
605     );
606
607     subtest 'holds_queue update tests' => sub {
608
609         plan tests => 1;
610
611         my $biblio = $builder->build_sample_biblio;
612
613         t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
614
615         my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
616         $mock->mock( 'enqueue', sub {
617             my ( $self, $args ) = @_;
618             is_deeply(
619                 $args->{biblio_ids},
620                 [ $biblio->id ],
621                 '->cancel triggers a holds queue update for the related biblio'
622             );
623         } );
624
625         $builder->build_object(
626             {
627                 class => 'Koha::Holds',
628                 value => {
629                     biblionumber   => $biblio->id,
630                 }
631             }
632         )->cancel;
633
634         # If the skip_holds_queue param is not honoured, then test count will fail.
635         $builder->build_object(
636             {
637                 class => 'Koha::Holds',
638                 value => {
639                     biblionumber   => $biblio->id,
640                 }
641             }
642         )->cancel({ skip_holds_queue => 1 });
643
644         t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
645
646         $builder->build_object(
647             {
648                 class => 'Koha::Holds',
649                 value => {
650                     biblionumber   => $biblio->id,
651                 }
652             }
653         )->cancel({ skip_holds_queue => 0 });
654     };
655
656     $schema->storage->txn_rollback;
657 };
658
659 subtest 'suspend_hold() and resume() tests' => sub {
660
661     plan tests => 2;
662
663     $schema->storage->txn_begin;
664
665     my $biblio = $builder->build_sample_biblio;
666     my $action;
667
668     t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 1 );
669
670     my $mock = Test::MockModule->new('Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue');
671     $mock->mock( 'enqueue', sub {
672         my ( $self, $args ) = @_;
673         is_deeply(
674             $args->{biblio_ids},
675             [ $biblio->id ],
676             "->$action triggers a holds queue update for the related biblio"
677         );
678     } );
679
680     my $hold = $builder->build_object(
681         {
682             class => 'Koha::Holds',
683             value => {
684                 biblionumber => $biblio->id,
685                 found        => undef,
686             }
687         }
688     );
689
690     $action = 'suspend_hold';
691     $hold->suspend_hold;
692
693     $action = 'resume';
694     $hold->resume;
695
696     $schema->storage->txn_rollback;
697 };
698
699 subtest 'cancellation_requests() and add_cancellation_request() tests' => sub {
700
701     plan tests => 4;
702
703     $schema->storage->txn_begin;
704
705     t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 );
706
707     my $hold = $builder->build_object( { class => 'Koha::Holds', } );
708
709     is( $hold->cancellation_requests->count, 0 );
710
711     # Add two cancellation requests
712     my $request_1 = $hold->add_cancellation_request;
713     isnt( $request_1->creation_date, undef, 'creation_date is set' );
714
715     my $requester     = $builder->build_object( { class => 'Koha::Patrons' } );
716     my $creation_date = '2021-06-25 14:05:35';
717
718     my $request_2 = $hold->add_cancellation_request(
719         {
720             creation_date => $creation_date,
721         }
722     );
723
724     is( $request_2->creation_date, $creation_date, 'Passed creation_date set' );
725
726     is( $hold->cancellation_requests->count, 2 );
727
728     $schema->storage->txn_rollback;
729 };
730
731 subtest 'cancellation_requestable_from_opac() tests' => sub {
732
733     plan tests => 5;
734
735     $schema->storage->txn_begin;
736
737     my $category =
738       $builder->build_object( { class => 'Koha::Patron::Categories' } );
739     my $item_home_library =
740       $builder->build_object( { class => 'Koha::Libraries' } );
741     my $patron_home_library =
742       $builder->build_object( { class => 'Koha::Libraries' } );
743
744     my $item =
745       $builder->build_sample_item( { library => $item_home_library->id } );
746     my $patron = $builder->build_object(
747         {
748             class => 'Koha::Patrons',
749             value => { branchcode => $patron_home_library->id }
750         }
751     );
752
753     subtest 'Exception cases' => sub {
754
755         plan tests => 4;
756
757         my $hold = $builder->build_object(
758             {
759                 class => 'Koha::Holds',
760                 value => {
761                     itemnumber     => undef,
762                     found          => undef,
763                     borrowernumber => $patron->id
764                 }
765             }
766         );
767
768         throws_ok { $hold->cancellation_requestable_from_opac; }
769         'Koha::Exceptions::InvalidStatus',
770           'Exception thrown because hold is not waiting';
771
772         is( $@->invalid_status, 'hold_not_waiting' );
773
774         $hold = $builder->build_object(
775             {
776                 class => 'Koha::Holds',
777                 value => {
778                     itemnumber     => undef,
779                     found          => 'W',
780                     borrowernumber => $patron->id
781                 }
782             }
783         );
784
785         throws_ok { $hold->cancellation_requestable_from_opac; }
786         'Koha::Exceptions::InvalidStatus',
787           'Exception thrown because waiting hold has no item linked';
788
789         is( $@->invalid_status, 'no_item_linked' );
790     };
791
792     # set default rule to enabled
793     Koha::CirculationRules->set_rule(
794         {
795             categorycode => '*',
796             itemtype     => '*',
797             branchcode   => '*',
798             rule_name    => 'waiting_hold_cancellation',
799             rule_value   => 1,
800         }
801     );
802
803     my $hold = $builder->build_object(
804         {
805             class => 'Koha::Holds',
806             value => {
807                 itemnumber     => $item->id,
808                 found          => 'W',
809                 borrowernumber => $patron->id
810             }
811         }
812     );
813
814     t::lib::Mocks::mock_preference( 'ReservesControlBranch',
815         'ItemHomeLibrary' );
816
817     Koha::CirculationRules->set_rule(
818         {
819             categorycode => $patron->categorycode,
820             itemtype     => $item->itype,
821             branchcode   => $item->homebranch,
822             rule_name    => 'waiting_hold_cancellation',
823             rule_value   => 0,
824         }
825     );
826
827     ok( !$hold->cancellation_requestable_from_opac );
828
829     Koha::CirculationRules->set_rule(
830         {
831             categorycode => $patron->categorycode,
832             itemtype     => $item->itype,
833             branchcode   => $item->homebranch,
834             rule_name    => 'waiting_hold_cancellation',
835             rule_value   => 1,
836         }
837     );
838
839     ok(
840         $hold->cancellation_requestable_from_opac,
841         'Make sure it is picking the right circulation rule'
842     );
843
844     t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
845
846     Koha::CirculationRules->set_rule(
847         {
848             categorycode => $patron->categorycode,
849             itemtype     => $item->itype,
850             branchcode   => $patron->branchcode,
851             rule_name    => 'waiting_hold_cancellation',
852             rule_value   => 0,
853         }
854     );
855
856     ok( !$hold->cancellation_requestable_from_opac );
857
858     Koha::CirculationRules->set_rule(
859         {
860             categorycode => $patron->categorycode,
861             itemtype     => $item->itype,
862             branchcode   => $patron->branchcode,
863             rule_name    => 'waiting_hold_cancellation',
864             rule_value   => 1,
865         }
866     );
867
868     ok(
869         $hold->cancellation_requestable_from_opac,
870         'Make sure it is picking the right circulation rule'
871     );
872
873     $schema->storage->txn_rollback;
874 };
875
876 subtest 'can_update_pickup_location_opac() tests' => sub {
877
878     plan tests => 8;
879
880     $schema->storage->txn_begin;
881
882     my $hold = $builder->build_object(
883         {   class => 'Koha::Holds',
884             value => { found => undef, suspend => 0, suspend_until => undef, waitingdate => undef }
885         }
886     );
887
888     t::lib::Mocks::mock_preference( 'OPACAllowUserToChangeBranch', '' );
889     $hold->found(undef);
890     is( $hold->can_update_pickup_location_opac, 0, "Pending hold pickup can't be changed (No change allowed)" );
891
892     $hold->found('T');
893     is( $hold->can_update_pickup_location_opac, 0, "In transit hold pickup can't be changed (No change allowed)" );
894
895     $hold->found('W');
896     is( $hold->can_update_pickup_location_opac, 0, "Waiting hold pickup can't be changed (No change allowed)" );
897
898     $hold->found(undef);
899     my $dt = dt_from_string();
900
901     $hold->suspend_hold( $dt );
902     is( $hold->can_update_pickup_location_opac, 0, "Suspended hold pickup can't be changed (No change allowed)" );
903     $hold->resume();
904
905     t::lib::Mocks::mock_preference( 'OPACAllowUserToChangeBranch', 'pending,intransit,suspended' );
906     $hold->found(undef);
907     is( $hold->can_update_pickup_location_opac, 1, "Pending hold pickup can be changed (pending,intransit,suspended allowed)" );
908
909     $hold->found('T');
910     is( $hold->can_update_pickup_location_opac, 1, "In transit hold pickup can be changed (pending,intransit,suspended allowed)" );
911
912     $hold->found('W');
913     is( $hold->can_update_pickup_location_opac, 0, "Waiting hold pickup can't be changed (pending,intransit,suspended allowed)" );
914
915     $hold->found(undef);
916     $dt = dt_from_string();
917     $hold->suspend_hold( $dt );
918     is( $hold->can_update_pickup_location_opac, 1, "Suspended hold pickup can be changed (pending,intransit,suspended allowed)" );
919
920     $schema->storage->txn_rollback;
921 };
922
923 subtest 'Koha::Hold::item_group tests' => sub {
924
925     plan tests => 1;
926
927     $schema->storage->txn_begin;
928
929     my $library  = $builder->build_object( { class => 'Koha::Libraries' } );
930     my $category = $builder->build_object(
931         {
932             class => 'Koha::Patron::Categories',
933             value => { exclude_from_local_holds_priority => 0 }
934         }
935     );
936     my $patron = $builder->build_object(
937         {
938             class => "Koha::Patrons",
939             value => {
940                 branchcode   => $library->branchcode,
941                 categorycode => $category->categorycode
942             }
943         }
944     );
945     my $biblio = $builder->build_sample_biblio();
946
947     my $item_group =
948       Koha::Biblio::ItemGroup->new( { biblio_id => $biblio->id } )->store();
949
950     my $hold = $builder->build_object(
951         {
952             class => "Koha::Holds",
953             value => {
954                 borrowernumber => $patron->borrowernumber,
955                 biblionumber   => $biblio->biblionumber,
956                 priority       => 1,
957                 item_group_id  => $item_group->id,
958             }
959         }
960     );
961
962     is( $hold->item_group->id, $item_group->id, "Got correct item group" );
963
964     $schema->storage->txn_rollback;
965 };