Bug 29525: Make Koha::Hold->cancel anonymize if required
[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 => 5;
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::Holds;
33 use Koha::Libraries;
34 use Koha::Old::Holds;
35
36 my $schema  = Koha::Database->new->schema;
37 my $builder = t::lib::TestBuilder->new;
38
39 subtest 'fill() tests' => sub {
40
41     plan tests => 11;
42
43     $schema->storage->txn_begin;
44
45     my $fee = 15;
46
47     my $category = $builder->build_object(
48         {
49             class => 'Koha::Patron::Categories',
50             value => { reservefee => $fee }
51         }
52     );
53     my $patron = $builder->build_object(
54         {
55             class => 'Koha::Patrons',
56             value => { categorycode => $category->id }
57         }
58     );
59     my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
60
61     my $title  = 'Do what you want';
62     my $biblio = $builder->build_sample_biblio( { title => $title } );
63     my $item   = $builder->build_sample_item( { biblionumber => $biblio->id } );
64     my $hold   = $builder->build_object(
65         {
66             class => 'Koha::Holds',
67             value => {
68                 biblionumber   => $biblio->id,
69                 borrowernumber => $patron->id,
70                 itemnumber     => $item->id,
71                 priority       => 10,
72             }
73         }
74     );
75
76     t::lib::Mocks::mock_preference( 'HoldFeeMode', 'any_time_is_collected' );
77     t::lib::Mocks::mock_preference( 'HoldsLog',    1 );
78     t::lib::Mocks::mock_userenv(
79         { patron => $manager, branchcode => $manager->branchcode } );
80
81     my $interface = 'api';
82     C4::Context->interface($interface);
83
84     my $ret = $hold->fill;
85
86     is( ref($ret), 'Koha::Hold', '->fill returns the object type' );
87     is( $ret->id, $hold->id, '->fill returns the object' );
88
89     is( Koha::Holds->find($hold->id), undef, 'Hold no longer current' );
90     my $old_hold = Koha::Old::Holds->find( $hold->id );
91
92     is( $old_hold->id, $hold->id, 'reserve_id retained' );
93     is( $old_hold->priority, 0, 'priority set to 0' );
94     is( $old_hold->found, 'F', 'found set to F' );
95
96     subtest 'fee applied tests' => sub {
97
98         plan tests => 9;
99
100         my $account = $patron->account;
101         is( $account->balance, $fee, 'Charge applied correctly' );
102
103         my $debits = $account->outstanding_debits;
104         is( $debits->count, 1, 'Only one fee charged' );
105
106         my $fee_debit = $debits->next;
107         is( $fee_debit->amount * 1, $fee, 'Fee amount stored correctly' );
108         is( $fee_debit->description, $title,
109             'Fee description stored correctly' );
110         is( $fee_debit->manager_id, $manager->id,
111             'Fee manager_id stored correctly' );
112         is( $fee_debit->branchcode, $manager->branchcode,
113             'Fee branchcode stored correctly' );
114         is( $fee_debit->interface, $interface,
115             'Fee interface stored correctly' );
116         is( $fee_debit->debit_type_code,
117             'RESERVE', 'Fee debit_type_code stored correctly' );
118         is( $fee_debit->itemnumber, $item->id,
119             'Fee itemnumber stored correctly' );
120     };
121
122     my $logs = Koha::ActionLogs->search(
123         {
124             action => 'FILL',
125             module => 'HOLDS',
126             object => $hold->id
127         }
128     );
129
130     is( $logs->count, 1, '1 log line added' );
131
132     # Set HoldFeeMode to something other than any_time_is_collected
133     t::lib::Mocks::mock_preference( 'HoldFeeMode', 'not_always' );
134     # Disable logging
135     t::lib::Mocks::mock_preference( 'HoldsLog',    0 );
136
137     $hold = $builder->build_object(
138         {
139             class => 'Koha::Holds',
140             value => {
141                 biblionumber   => $biblio->id,
142                 borrowernumber => $patron->id,
143                 itemnumber     => $item->id,
144                 priority       => 10,
145             }
146         }
147     );
148
149     $hold->fill;
150
151     my $account = $patron->account;
152     is( $account->balance, $fee, 'No new charge applied' );
153
154     my $debits = $account->outstanding_debits;
155     is( $debits->count, 1, 'Only one fee charged, because of HoldFeeMode' );
156
157     $logs = Koha::ActionLogs->search(
158         {
159             action => 'FILL',
160             module => 'HOLDS',
161             object => $hold->id
162         }
163     );
164
165     is( $logs->count, 0, 'HoldsLog disabled, no logs added' );
166
167     $schema->storage->txn_rollback;
168 };
169
170 subtest 'patron() tests' => sub {
171
172     plan tests => 2;
173
174     $schema->storage->txn_begin;
175
176     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
177     my $hold   = $builder->build_object(
178         {
179             class => 'Koha::Holds',
180             value => {
181                 borrowernumber => $patron->borrowernumber
182             }
183         }
184     );
185
186     my $hold_patron = $hold->patron;
187     is( ref($hold_patron), 'Koha::Patron', 'Right type' );
188     is( $hold_patron->id, $patron->id, 'Right object' );
189
190     $schema->storage->txn_rollback;
191 };
192
193 subtest 'set_pickup_location() tests' => sub {
194
195     plan tests => 11;
196
197     $schema->storage->txn_begin;
198
199     my $mock_biblio = Test::MockModule->new('Koha::Biblio');
200     my $mock_item   = Test::MockModule->new('Koha::Item');
201
202     my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
203     my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
204     my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
205
206     # let's control what Koha::Biblio->pickup_locations returns, for testing
207     $mock_biblio->mock( 'pickup_locations', sub {
208         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
209     });
210     # let's mock what Koha::Item->pickup_locations returns, for testing
211     $mock_item->mock( 'pickup_locations', sub {
212         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
213     });
214
215     my $biblio = $builder->build_sample_biblio;
216     my $item   = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
217
218     # Test biblio-level holds
219     my $biblio_hold = $builder->build_object(
220         {
221             class => "Koha::Holds",
222             value => {
223                 biblionumber => $biblio->biblionumber,
224                 branchcode   => $library_3->branchcode,
225                 itemnumber   => undef,
226             }
227         }
228     );
229
230     throws_ok
231         { $biblio_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
232         'Koha::Exceptions::Hold::InvalidPickupLocation',
233         'Exception thrown on invalid pickup location';
234
235     $biblio_hold->discard_changes;
236     is( $biblio_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
237
238     my $ret = $biblio_hold->set_pickup_location({ library_id => $library_2->id });
239     is( ref($ret), 'Koha::Hold', 'self is returned' );
240
241     $biblio_hold->discard_changes;
242     is( $biblio_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
243
244     # Test item-level holds
245     my $item_hold = $builder->build_object(
246         {
247             class => "Koha::Holds",
248             value => {
249                 biblionumber => $biblio->biblionumber,
250                 branchcode   => $library_3->branchcode,
251                 itemnumber   => $item->itemnumber,
252             }
253         }
254     );
255
256     throws_ok
257         { $item_hold->set_pickup_location({ library_id => $library_1->branchcode }); }
258         'Koha::Exceptions::Hold::InvalidPickupLocation',
259         'Exception thrown on invalid pickup location';
260
261     $item_hold->discard_changes;
262     is( $item_hold->branchcode, $library_3->branchcode, 'branchcode remains untouched' );
263
264     $item_hold->set_pickup_location({ library_id => $library_1->branchcode, force => 1 });
265     $item_hold->discard_changes;
266     is( $item_hold->branchcode, $library_1->branchcode, 'branchcode changed because of \'force\'' );
267
268     $ret = $item_hold->set_pickup_location({ library_id => $library_2->id });
269     is( ref($ret), 'Koha::Hold', 'self is returned' );
270
271     $item_hold->discard_changes;
272     is( $item_hold->branchcode, $library_2->id, 'Pickup location changed correctly' );
273
274     throws_ok
275         { $item_hold->set_pickup_location({ library_id => undef }); }
276         'Koha::Exceptions::MissingParameter',
277         'Exception thrown if missing parameter';
278
279     is( "$@", 'The library_id parameter is mandatory', 'Exception message is clear' );
280
281     $schema->storage->txn_rollback;
282 };
283
284 subtest 'is_pickup_location_valid() tests' => sub {
285
286     plan tests => 5;
287
288     $schema->storage->txn_begin;
289
290     my $mock_biblio = Test::MockModule->new('Koha::Biblio');
291     my $mock_item   = Test::MockModule->new('Koha::Item');
292
293     my $library_1 = $builder->build_object({ class => 'Koha::Libraries' });
294     my $library_2 = $builder->build_object({ class => 'Koha::Libraries' });
295     my $library_3 = $builder->build_object({ class => 'Koha::Libraries' });
296
297     # let's control what Koha::Biblio->pickup_locations returns, for testing
298     $mock_biblio->mock( 'pickup_locations', sub {
299         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
300     });
301     # let's mock what Koha::Item->pickup_locations returns, for testing
302     $mock_item->mock( 'pickup_locations', sub {
303         return Koha::Libraries->search( { branchcode => [ $library_2->branchcode, $library_3->branchcode ] } );
304     });
305
306     my $biblio = $builder->build_sample_biblio;
307     my $item   = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
308
309     # Test biblio-level holds
310     my $biblio_hold = $builder->build_object(
311         {
312             class => "Koha::Holds",
313             value => {
314                 biblionumber => $biblio->biblionumber,
315                 branchcode   => $library_3->branchcode,
316                 itemnumber   => undef,
317             }
318         }
319     );
320
321     ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
322     ok( $biblio_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid');
323
324     # Test item-level holds
325     my $item_hold = $builder->build_object(
326         {
327             class => "Koha::Holds",
328             value => {
329                 biblionumber => $biblio->biblionumber,
330                 branchcode   => $library_3->branchcode,
331                 itemnumber   => $item->itemnumber,
332             }
333         }
334     );
335
336     ok( !$item_hold->is_pickup_location_valid({ library_id => $library_1->branchcode }), 'Pickup location invalid');
337     ok( $item_hold->is_pickup_location_valid({ library_id => $library_2->id }), 'Pickup location valid' );
338
339     subtest 'pickup_locations() returning ->empty' => sub {
340
341         plan tests => 2;
342
343         $schema->storage->txn_begin;
344
345         my $library = $builder->build_object({ class => 'Koha::Libraries' });
346
347         my $mock_item = Test::MockModule->new('Koha::Item');
348         $mock_item->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
349
350         my $mock_biblio = Test::MockModule->new('Koha::Biblio');
351         $mock_biblio->mock( 'pickup_locations', sub { return Koha::Libraries->new->empty; } );
352
353         my $item   = $builder->build_sample_item();
354         my $biblio = $item->biblio;
355
356         # Test biblio-level holds
357         my $biblio_hold = $builder->build_object(
358             {
359                 class => "Koha::Holds",
360                 value => {
361                     biblionumber => $biblio->biblionumber,
362                     itemnumber   => undef,
363                 }
364             }
365         );
366
367         ok( !$biblio_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
368
369         # Test item-level holds
370         my $item_hold = $builder->build_object(
371             {
372                 class => "Koha::Holds",
373                 value => {
374                     biblionumber => $biblio->biblionumber,
375                     itemnumber   => $item->itemnumber,
376                 }
377             }
378         );
379
380         ok( !$item_hold->is_pickup_location_valid({ library_id => $library->branchcode }), 'Pickup location invalid');
381
382         $schema->storage->txn_rollback;
383     };
384
385     $schema->storage->txn_rollback;
386 };
387
388 subtest 'cancel() tests' => sub {
389
390     plan tests => 4;
391
392     $schema->storage->txn_begin;
393
394     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
395
396     # reduce the tests noise
397     t::lib::Mocks::mock_preference( 'HoldsLog', 0 );
398     t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelayCharge',
399         undef );
400
401     t::lib::Mocks::mock_preference( 'AnonymousPatron', undef );
402
403     # 0 == keep forever
404     $patron->privacy(0)->store;
405     my $hold = $builder->build_object(
406         {
407             class => 'Koha::Holds',
408             value => { borrowernumber => $patron->id, status => undef }
409         }
410     );
411     $hold->cancel();
412     is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
413         $patron->borrowernumber, 'Patron link is kept' );
414
415     # 1 == "default", meaning it is not protected from removal
416     $patron->privacy(1)->store;
417     $hold = $builder->build_object(
418         {
419             class => 'Koha::Holds',
420             value => { borrowernumber => $patron->id, status => undef }
421         }
422     );
423     $hold->cancel();
424     is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
425         $patron->borrowernumber, 'Patron link is kept' );
426
427     # 2 == delete immediately
428     $patron->privacy(2)->store;
429     $hold = $builder->build_object(
430         {
431             class => 'Koha::Holds',
432             value => { borrowernumber => $patron->id, status => undef }
433         }
434     );
435     $hold->cancel();
436     is( Koha::Old::Holds->find( $hold->id )->borrowernumber,
437         undef, 'Patron link is deleted immediately' );
438
439     my $anonymous_patron = $builder->build_object({ class => 'Koha::Patrons' });
440     t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron->id );
441
442     $hold = $builder->build_object(
443         {
444             class => 'Koha::Holds',
445             value => { borrowernumber => $patron->id, status => undef }
446         }
447     );
448     $hold->cancel();
449     is(
450         Koha::Old::Holds->find( $hold->id )->borrowernumber,
451         $anonymous_patron->id,
452         'Patron link is set to the configured anonymous patron immediately'
453     );
454
455     $schema->storage->txn_rollback;
456 };