Bug 34893: (QA follow-up) Tidy code for qa script
[koha.git] / t / db_dependent / ILSDI_Services.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 CGI qw ( -utf8 );
21
22 use Test::More tests => 12;
23 use Test::MockModule;
24 use t::lib::Mocks;
25 use t::lib::TestBuilder;
26 use t::lib::Dates;
27 use XML::LibXML;
28
29 use C4::Items qw( ModItemTransfer );
30 use C4::Circulation qw( AddIssue );
31 use C4::Reserves qw (AddReserve ModReserve ModReserveAffect ModReserveStatus);
32
33 use Koha::AuthUtils;
34 use Koha::DateUtils qw( dt_from_string );
35
36 BEGIN {
37     use_ok('C4::ILSDI::Services', qw( AuthenticatePatron GetPatronInfo LookupPatron HoldTitle HoldItem GetRecords RenewLoan GetAvailability ));
38 }
39
40 my $schema  = Koha::Database->schema;
41 my $dbh     = C4::Context->dbh;
42 my $builder = t::lib::TestBuilder->new;
43
44 subtest 'AuthenticatePatron test' => sub {
45
46     plan tests => 18;
47
48     $schema->storage->txn_begin;
49
50     my $plain_password = 'tomasito';
51
52     $builder->build({
53         source => 'Borrower',
54         value => {
55             cardnumber => undef,
56         }
57     });
58
59     my $borrower = $builder->build({
60         source => 'Borrower',
61         value  => {
62             cardnumber => undef,
63             password => Koha::AuthUtils::hash_password( $plain_password ),
64             lastseen => "2001-01-01 12:34:56"
65         }
66     });
67
68     my $query = CGI->new;
69     $query->param( 'username', $borrower->{userid});
70     $query->param( 'password', $plain_password);
71
72     t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '' );
73     my $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
74     is( $reply->{id}, $borrower->{borrowernumber}, "userid and password - Patron authenticated" );
75     is( $reply->{code}, undef, "Error code undef");
76     my $seen_patron = Koha::Patrons->find({ borrowernumber => $reply->{id} });
77     is( $seen_patron->lastseen(), '2001-01-01 12:34:56','Last seen not updated if not tracking patrons');
78
79     $query->param('password','ilsdi-passworD');
80     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
81     is( $reply->{code}, 'PatronNotFound', "userid and wrong password - PatronNotFound" );
82     is( $reply->{id}, undef, "id undef");
83
84     $query->param( 'password', $plain_password );
85     $query->param( 'username', 'wrong-ilsdi-useriD' );
86     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
87     is( $reply->{code}, 'PatronNotFound', "non-existing userid - PatronNotFound" );
88     is( $reply->{id}, undef, "id undef");
89
90     t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '1' );
91     $query->param( 'username', uc( $borrower->{userid} ));
92     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
93     my $now = dt_from_string;
94     is( $reply->{id}, $borrower->{borrowernumber}, "userid is not case sensitive - Patron authenticated" );
95     is( $reply->{code}, undef, "Error code undef");
96     $seen_patron = Koha::Patrons->find({ borrowernumber => $reply->{id} });
97     is( t::lib::Dates::compare( $seen_patron->lastseen, $now), 0, 'Last seen updated to today if tracking patrons' );
98
99     $query->param( 'username', $borrower->{cardnumber} );
100     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
101     is( $reply->{id}, $borrower->{borrowernumber}, "cardnumber and password - Patron authenticated" );
102     is( $reply->{code}, undef, "Error code undef" );
103
104     $query->param( 'password', 'ilsdi-passworD' );
105     $reply = C4::ILSDI::Services::AuthenticatePatron( $query );
106     is( $reply->{code}, 'PatronNotFound', "cardnumber and wrong password - PatronNotFount" );
107     is( $reply->{id}, undef, "id undef" );
108
109     $query->param( 'username', 'randomcardnumber1234' );
110     $query->param( 'password', $plain_password );
111     $reply = C4::ILSDI::Services::AuthenticatePatron($query);
112     is( $reply->{code}, 'PatronNotFound', "non-existing cardnumer/userid - PatronNotFound" );
113     is( $reply->{id}, undef, "id undef");
114
115     $query->param( 'username', $borrower->{userid} );
116     $query->param( 'password', $plain_password );
117     $seen_patron->password_expiration_date('2020-01-01')->store;
118     $reply = C4::ILSDI::Services::AuthenticatePatron($query);
119     is( $reply->{code}, 'PasswordExpired', "correct credentials, expired password not authenticated" );
120     is( $reply->{id}, undef, "id undef");
121
122     $schema->storage->txn_rollback;
123 };
124
125
126 subtest 'GetPatronInfo/GetBorrowerAttributes test for extended patron attributes' => sub {
127
128     plan tests => 5;
129
130     $schema->storage->txn_begin;
131
132     $schema->resultset( 'Issue' )->delete_all;
133     $schema->resultset( 'Borrower' )->delete_all;
134     $schema->resultset( 'BorrowerAttribute' )->delete_all;
135     $schema->resultset( 'BorrowerAttributeType' )->delete_all;
136     $schema->resultset( 'Category' )->delete_all;
137     $schema->resultset( 'Item' )->delete_all; # 'Branch' deps. on this
138     $schema->resultset( 'Club' )->delete_all;
139     $schema->resultset( 'Branch' )->delete_all;
140
141     # Configure Koha to enable ILS-DI server and extended attributes:
142     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
143     t::lib::Mocks::mock_preference( 'ExtendedPatronAttributes', 1 );
144
145     # Set up a library/branch for our user to belong to:
146     my $lib = $builder->build( {
147         source => 'Branch',
148         value => {
149             branchcode => 'T_ILSDI',
150         }
151     } );
152
153     # Create a new category for user to belong to:
154     my $cat = $builder->build( {
155         source => 'Category',
156         value  => {
157             category_type                 => 'A',
158             BlockExpiredPatronOpacActions => -1,
159         }
160     } );
161
162     # Create a new attribute type:
163     my $attr_type = $builder->build( {
164         source => 'BorrowerAttributeType',
165         value  => {
166             code                      => 'HIDEME',
167             opac_display              => 0,
168             authorised_value_category => '',
169             class                     => '',
170         }
171     } );
172     my $attr_type_visible = $builder->build( {
173         source => 'BorrowerAttributeType',
174         value  => {
175             code                      => 'SHOWME',
176             opac_display              => 1,
177             authorised_value_category => '',
178             class                     => '',
179         }
180     } );
181
182     # Create a new user:
183     my $brwr = $builder->build( {
184         source => 'Borrower',
185         value  => {
186             categorycode => $cat->{'categorycode'},
187             branchcode   => $lib->{'branchcode'},
188         }
189     } );
190
191     # Authorised value:
192     my $auth = $builder->build( {
193         source => 'AuthorisedValue',
194         value  => {
195             category => $cat->{'categorycode'}
196         }
197     } );
198
199     # Set the new attribute for our user:
200     my $attr_hidden = $builder->build( {
201         source => 'BorrowerAttribute',
202         value  => {
203             borrowernumber => $brwr->{'borrowernumber'},
204             code           => $attr_type->{'code'},
205             attribute      => '1337 hidden',
206         }
207     } );
208     my $attr_shown = $builder->build( {
209         source => 'BorrowerAttribute',
210         value  => {
211             borrowernumber => $brwr->{'borrowernumber'},
212             code           => $attr_type_visible->{'code'},
213             attribute      => '1337 shown',
214         }
215     } );
216
217     my $fine = $builder->build(
218         {
219             source => 'Accountline',
220             value  => {
221                 borrowernumber    => $brwr->{borrowernumber},
222                 debit_type_code   => 'OVERDUE',
223                 amountoutstanding => 10
224             }
225         }
226     );
227
228     # Prepare and send web request for IL-SDI server:
229     my $query = CGI->new;
230     $query->param( 'service', 'GetPatronInfo' );
231     $query->param( 'patron_id', $brwr->{'borrowernumber'} );
232     $query->param( 'show_attributes', '1' );
233     $query->param( 'show_fines', '1' );
234
235     my $reply = C4::ILSDI::Services::GetPatronInfo( $query );
236
237     # Build a structure for comparison:
238     my $cmp = {
239         borrowernumber    => $brwr->{borrowernumber},
240         value             => $attr_shown->{'attribute'},
241         value_description => $attr_shown->{'attribute'},
242         %$attr_type_visible,
243         %$attr_shown,
244     };
245
246     is( $reply->{'charges'}, '10.00',
247         'The \'charges\' attribute should be correctly filled (bug 17836)' );
248
249     is( scalar( @{$reply->{fines}->{fine}}), 1, 'There should be only 1 account line');
250     is(
251         $reply->{fines}->{fine}->[0]->{accountlines_id},
252         $fine->{accountlines_id},
253         "The accountline should be the correct one"
254     );
255
256     # Check results:
257     is_deeply( $reply->{'attributes'}, [ $cmp ], 'Test GetPatronInfo - show_attributes parameter' );
258
259     ok( exists $reply->{is_expired}, 'There should be the is_expired information');
260
261     # Cleanup
262     $schema->storage->txn_rollback;
263 };
264
265 subtest 'LookupPatron test' => sub {
266
267     plan tests => 9;
268
269     $schema->storage->txn_begin;
270
271     $schema->resultset( 'Issue' )->delete_all;
272     $schema->resultset( 'Borrower' )->delete_all;
273     $schema->resultset( 'BorrowerAttribute' )->delete_all;
274     $schema->resultset( 'BorrowerAttributeType' )->delete_all;
275     $schema->resultset( 'Category' )->delete_all;
276     $schema->resultset( 'Item' )->delete_all; # 'Branch' deps. on this
277     $schema->resultset( 'Branch' )->delete_all;
278
279     my $borrower = $builder->build({
280         source => 'Borrower',
281     });
282
283     my $query = CGI->new();
284     my $bad_result = C4::ILSDI::Services::LookupPatron($query);
285     is( $bad_result->{message}, 'PatronNotFound', 'No parameters' );
286
287     $query->delete_all();
288     $query->param( 'id', $borrower->{firstname} );
289     my $optional_result = C4::ILSDI::Services::LookupPatron($query);
290     is(
291         $optional_result->{id},
292         $borrower->{borrowernumber},
293         'Valid Firstname only'
294     );
295
296     $query->delete_all();
297     $query->param( 'id', 'ThereIsNoWayThatThisCouldPossiblyBeValid' );
298     my $bad_optional_result = C4::ILSDI::Services::LookupPatron($query);
299     is( $bad_optional_result->{message}, 'PatronNotFound', 'Invalid ID' );
300
301     foreach my $id_type (
302         'cardnumber',
303         'userid',
304         'email',
305         'borrowernumber',
306         'surname',
307         'firstname'
308     ) {
309         $query->delete_all();
310         $query->param( 'id_type', $id_type );
311         $query->param( 'id', $borrower->{$id_type} );
312         my $result = C4::ILSDI::Services::LookupPatron($query);
313         is( $result->{'id'}, $borrower->{borrowernumber}, "Checking $id_type" );
314     }
315
316     # Cleanup
317     $schema->storage->txn_rollback;
318 };
319
320 subtest 'Holds test' => sub {
321
322     plan tests => 9;
323
324     $schema->storage->txn_begin;
325
326     t::lib::Mocks::mock_preference( 'AllowHoldsOnDamagedItems', 0 );
327
328     my $patron = $builder->build({
329         source => 'Borrower',
330     });
331
332     my $item = $builder->build_sample_item(
333         {
334             damaged => 1
335         }
336     );
337
338     my $query = CGI->new;
339     $query->param( 'patron_id', $patron->{borrowernumber});
340     $query->param( 'bib_id', $item->biblionumber);
341
342     my $reply = C4::ILSDI::Services::HoldTitle( $query );
343     is( $reply->{code}, 'damaged', "Item damaged" );
344
345     $item->damaged(0)->store;
346
347     my $hold = $builder->build({
348         source => 'Reserve',
349         value => {
350             borrowernumber => $patron->{borrowernumber},
351             biblionumber => $item->biblionumber,
352             itemnumber => $item->itemnumber
353         }
354     });
355
356     $reply = C4::ILSDI::Services::HoldTitle( $query );
357     is( $reply->{code}, 'itemAlreadyOnHold', "Item already on hold" );
358
359     my $biblio_with_no_item = $builder->build_sample_biblio;
360
361     $query = CGI->new;
362     $query->param( 'patron_id', $patron->{borrowernumber});
363     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
364
365     $reply = C4::ILSDI::Services::HoldTitle( $query );
366     is( $reply->{code}, 'NoItems', 'Biblio has no item' );
367
368     my $item2 = $builder->build_sample_item(
369         {
370             damaged => 0,
371         }
372     );
373
374     t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
375     Koha::CirculationRules->set_rule(
376         {
377             categorycode => $patron->{categorycode},
378             itemtype     => $item2->{itype},
379             branchcode   => $patron->{branchcode},
380             rule_name    => 'reservesallowed',
381             rule_value   => 1,
382         }
383     );
384
385     $query = CGI->new;
386     $query->param( 'patron_id', $patron->{borrowernumber});
387     $query->param( 'bib_id', $item2->biblionumber);
388     $query->param( 'item_id', $item2->itemnumber);
389
390     $reply = C4::ILSDI::Services::HoldItem( $query );
391     is( $reply->{code}, 'tooManyReserves', "Too many reserves" );
392
393     Koha::CirculationRules->set_rule(
394         {
395             categorycode => $patron->{categorycode},
396             itemtype     => $item2->{itype},
397             branchcode   => $patron->{branchcode},
398             rule_name    => 'reservesallowed',
399             rule_value   => 0,
400         }
401     );
402
403     $query = CGI->new;
404     $query->param( 'patron_id', $patron->{borrowernumber});
405     $query->param( 'bib_id', $item2->biblionumber);
406     $query->param( 'item_id', $item2->itemnumber);
407
408     $reply = C4::ILSDI::Services::HoldItem( $query );
409     is( $reply->{code}, 'noReservesAllowed', "No reserves allowed" );
410
411     my $origin_branch = $builder->build(
412         {
413             source => 'Branch',
414             value  => {
415                 pickup_location => 1,
416             }
417         }
418     );
419
420     # Adding a holdable item.
421     my $item3 = $builder->build_sample_item(
422        {
423            barcode => '123456789',
424            library => $origin_branch->{branchcode}
425        });
426
427     my $item4 = $builder->build_sample_item(
428         {
429            biblionumber => $item3->biblionumber,
430            damaged => 1,
431            library => $origin_branch->{branchcode}
432        });
433
434     Koha::CirculationRules->set_rule(
435         {
436             categorycode => $patron->{categorycode},
437             itemtype     => $item3->{itype},
438             branchcode   => $patron->{branchcode},
439             rule_name    => 'reservesallowed',
440             rule_value   => 10,
441         }
442     );
443
444     $query = CGI->new;
445     $query->param( 'patron_id', $patron->{borrowernumber});
446     $query->param( 'bib_id', $item4->biblionumber);
447     $query->param( 'item_id', $item4->itemnumber);
448
449     $reply = C4::ILSDI::Services::HoldItem( $query );
450     is( $reply->{code}, 'damaged', "Item is damaged" );
451
452     my $module = Test::MockModule->new('C4::Context');
453     $module->mock('userenv', sub { { patron => $patron } });
454     my $issue = C4::Circulation::AddIssue($patron, $item3->barcode);
455     t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', '0' );
456
457     $query = CGI->new;
458     $query->param( 'patron_id', $patron->{borrowernumber});
459     $query->param( 'bib_id', $item3->biblionumber);
460     $query->param( 'item_id', $item3->itemnumber);
461     $query->param( 'pickup_location', $origin_branch->{branchcode});
462     $reply = C4::ILSDI::Services::HoldItem( $query );
463
464     is( $reply->{code}, 'alreadypossession', "Patron has issued same book" );
465     is( $reply->{pickup_location}, undef, "No reserve placed");
466
467     # Test Patron cannot reserve if expired and BlockExpiredPatronOpacActions
468     my $category = $builder->build({
469         source => 'Category',
470         value => { BlockExpiredPatronOpacActions => -1 }
471         });
472
473     my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
474
475     my $expired_borrowernumber = Koha::Patron->new({
476         firstname =>  'Expired',
477         surname => 'Patron',
478         categorycode => $category->{categorycode},
479         branchcode => $branch_1,
480         dateexpiry => '2000-01-01',
481     })->store->borrowernumber;
482
483     t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
484
485     my $item5 = $builder->build({
486         source => 'Item',
487         value => {
488             biblionumber => $biblio_with_no_item->biblionumber,
489             damaged => 0,
490         }
491     });
492
493     $query = CGI->new;
494     $query->param( 'patron_id', $expired_borrowernumber);
495     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
496     $query->param( 'item_id', $item5->{itemnumber});
497
498     $reply = C4::ILSDI::Services::HoldItem( $query );
499     is( $reply->{code}, 'PatronExpired', "Patron is expired" );
500
501     $schema->storage->txn_rollback;
502 };
503
504 subtest 'Holds test for branch transfer limits' => sub {
505
506     plan tests => 6;
507
508     $schema->storage->txn_begin;
509
510     # Test enforement of branch transfer limits
511     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
512     t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
513
514     my $patron = $builder->build({
515         source => 'Borrower',
516     });
517
518     my $origin_branch = $builder->build(
519         {
520             source => 'Branch',
521             value  => {
522                 pickup_location => 1,
523             }
524         }
525     );
526     my $pickup_branch = $builder->build(
527         {
528             source => 'Branch',
529             value  => {
530                 pickup_location => 1,
531             }
532         }
533     );
534
535     my $item = $builder->build_sample_item(
536         {
537             library => $origin_branch->{branchcode},
538         }
539     );
540
541     Koha::CirculationRules->set_rule(
542         {
543             categorycode => undef,
544             itemtype     => undef,
545             branchcode   => undef,
546             rule_name    => 'reservesallowed',
547             rule_value   => 99,
548         }
549     );
550
551     my $limit = Koha::Item::Transfer::Limit->new({
552         toBranch => $pickup_branch->{branchcode},
553         fromBranch => $item->holdingbranch,
554         itemtype => $item->effective_itemtype,
555     })->store();
556
557     my $query = CGI->new;
558     $query->param( 'pickup_location', $pickup_branch->{branchcode} );
559     $query->param( 'patron_id', $patron->{borrowernumber});
560     $query->param( 'bib_id', $item->biblionumber);
561     $query->param( 'item_id', $item->itemnumber);
562
563     my $reply = C4::ILSDI::Services::HoldItem( $query );
564     is( $reply->{code}, 'cannotBeTransferred', "Item hold, Item cannot be transferred" );
565
566     $reply = C4::ILSDI::Services::HoldTitle( $query );
567     is( $reply->{code}, 'cannotBeTransferred', "Record hold, Item cannot be transferred" );
568
569     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
570
571     $reply = C4::ILSDI::Services::HoldItem( $query );
572     is( $reply->{code}, undef, "Item hold, Item can be transferred" );
573     my $hold = Koha::Holds->search({ itemnumber => $item->itemnumber, borrowernumber => $patron->{borrowernumber} })->next;
574     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
575
576     Koha::Holds->search()->delete();
577
578     $reply = C4::ILSDI::Services::HoldTitle( $query );
579     is( $reply->{code}, undef, "Record hold, Item con be transferred" );
580     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber, borrowernumber => $patron->{borrowernumber} })->next;
581     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
582
583     $schema->storage->txn_rollback;
584 };
585
586 subtest 'Holds test with start_date and end_date' => sub {
587
588     plan tests => 8;
589
590     $schema->storage->txn_begin;
591
592     my $pickup_library = $builder->build_object(
593         {
594             class  => 'Koha::Libraries',
595             value  => {
596                 pickup_location => 1,
597             }
598         }
599     );
600
601     my $patron = $builder->build_object({
602         class => 'Koha::Patrons',
603     });
604
605     my $item = $builder->build_sample_item({ library => $pickup_library->branchcode });
606
607     Koha::CirculationRules->set_rule(
608         {
609             categorycode => undef,
610             itemtype     => undef,
611             branchcode   => undef,
612             rule_name    => 'reservesallowed',
613             rule_value   => 99,
614         }
615     );
616
617     my $query = CGI->new;
618     $query->param( 'pickup_location', $pickup_library->branchcode );
619     $query->param( 'patron_id', $patron->borrowernumber);
620     $query->param( 'bib_id', $item->biblionumber);
621     $query->param( 'item_id', $item->itemnumber);
622     $query->param( 'start_date', '2020-03-20');
623     $query->param( 'expiry_date', '2020-04-22');
624
625     my $reply = C4::ILSDI::Services::HoldItem( $query );
626     is ($reply->{pickup_location}, $pickup_library->branchname, "Item hold with date parameters was placed");
627     my $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
628     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
629     is( $hold->reservedate, '2020-03-20', "Item hold has correct start date" );
630     is( $hold->expirationdate, '2020-04-22', "Item hold has correct end date" );
631
632     $hold->delete();
633
634     $reply = C4::ILSDI::Services::HoldTitle( $query );
635     is ($reply->{pickup_location}, $pickup_library->branchname, "Record hold with date parameters was placed");
636     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
637     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
638     is( $hold->reservedate, '2020-03-20', "Record hold has correct start date" );
639     is( $hold->expirationdate, '2020-04-22', "Record hold has correct end date" );
640
641     $schema->storage->txn_rollback;
642 };
643
644 subtest 'GetRecords' => sub {
645
646     plan tests => 8;
647
648     $schema->storage->txn_begin;
649
650     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
651
652     my $branch1 = $builder->build({
653         source => 'Branch',
654     });
655     my $branch2 = $builder->build({
656         source => 'Branch',
657     });
658
659     my $item = $builder->build_sample_item(
660         {
661             library => $branch1->{branchcode},
662         }
663     );
664
665     my $patron = $builder->build({
666         source => 'Borrower',
667     });
668
669     my $issue = $builder->build({
670         source => 'Issue',
671         value => {
672             itemnumber => $item->itemnumber,
673         }
674     });
675
676     my $hold = $builder->build({
677         source => 'Reserve',
678         value => {
679             biblionumber => $item->biblionumber,
680         }
681     });
682
683     ModItemTransfer($item->itemnumber, $branch1->{branchcode}, $branch2->{branchcode}, 'Manual');
684
685     my $cgi = CGI->new;
686     $cgi->param(service => 'GetRecords');
687     $cgi->param(id => $item->biblionumber);
688
689     my $reply = C4::ILSDI::Services::GetRecords($cgi);
690
691     my $transfer = $item->get_transfer;
692     my $expected = {
693         datesent => $transfer->datesent,
694         frombranch => $transfer->frombranch,
695         tobranch => $transfer->tobranch,
696     };
697     is_deeply($reply->{record}->[0]->{items}->{item}->[0]->{transfer}, $expected,
698         'GetRecords returns transfer informations');
699
700     # Check informations exposed
701     my $reply_issue = $reply->{record}->[0]->{issues}->{issue}->[0];
702     is($reply_issue->{itemnumber}, $item->itemnumber, 'GetRecords has an issue tag');
703     is($reply_issue->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in issue tag');
704     is($reply_issue->{surname}, undef, 'GetRecords does not expose surname in issue tag');
705     is($reply_issue->{firstname}, undef, 'GetRecords does not expose firstname in issue tag');
706     is($reply_issue->{cardnumber}, undef, 'GetRecords does not expose cardnumber in issue tag');
707     my $reply_reserve = $reply->{record}->[0]->{reserves}->{reserve}->[0];
708     is($reply_reserve->{biblionumber}, $item->biblionumber, 'GetRecords has a reserve tag');
709     is($reply_reserve->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in reserve tag');
710
711     $schema->storage->txn_rollback;
712 };
713
714 subtest 'RenewHold' => sub {
715     plan tests => 4;
716
717     $schema->storage->txn_begin;
718
719     my $cgi    = CGI->new;
720     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
721     my $item   = $builder->build_sample_item;
722     $cgi->param( patron_id => $patron->borrowernumber );
723     $cgi->param( item_id   => $item->itemnumber );
724
725     t::lib::Mocks::mock_userenv( { patron => $patron } );    # For AddIssue
726     my $checkout = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
727
728     # Everything is ok
729     my $reply = C4::ILSDI::Services::RenewLoan($cgi);
730     is( exists $reply->{date_due}, 1, 'If the item is checked out, the date_due key should exist' );
731
732     # The item is not checked out
733     $checkout->delete;
734     $reply = C4::ILSDI::Services::RenewLoan($cgi);
735     is( $reply, undef, 'If the item is not checked out, we should not explode.');    # FIXME We should return an error code instead
736
737     # The item does not exist
738     $item->delete;
739     $reply = C4::ILSDI::Services::RenewLoan($cgi);
740     is( $reply->{code}, 'RecordNotFound', 'If the item does not exist, RecordNotFound should be returned');
741
742     $patron->delete;
743     $reply = C4::ILSDI::Services::RenewLoan($cgi);
744     is( $reply->{code}, 'PatronNotFound', 'If the patron does not exist, PatronNotFound should be returned');
745
746     $schema->storage->txn_rollback;
747 };
748
749 subtest 'CancelHold' => sub {
750     plan tests => 4;
751
752     $schema->storage->txn_begin;
753
754     my $cgi = CGI->new;
755
756     my $library = $builder->build_object({
757         class => 'Koha::Libraries',
758     });
759
760     my $patron = $builder->build_object( { class => 'Koha::Patrons',
761                                            value => {
762                                                branchcode => $library->branchcode,
763                                            },
764                                          } );
765
766     my $patron2 = $builder->build_object( { class => 'Koha::Patrons',
767                                            value => {
768                                                branchcode => $library->branchcode,
769                                            },
770                                          } );
771
772     my $item = $builder->build_sample_item({ library => $library->branchcode });
773
774     my $reserve = C4::Reserves::AddReserve({branchcode => $library->branchcode,
775                                             borrowernumber => $patron->borrowernumber,
776                                             biblionumber => $item->biblionumber });
777
778     # Affecting the reserve sets it to a waiting state
779     C4::Reserves::ModReserveAffect( $item->itemnumber,
780                                     $patron->borrowernumber,
781                                     undef,
782                                     $reserve,
783                                    );
784
785     $cgi->param( patron_id => $patron2->borrowernumber );
786     $cgi->param( item_id   => $reserve );
787
788
789     my $reply = C4::ILSDI::Services::CancelHold($cgi);
790     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If the patron is wrong, BorrowerCannotCancelHold should be returned');
791
792     $cgi->param( patron_id => $patron->borrowernumber );
793     $reply = C4::ILSDI::Services::CancelHold($cgi);
794     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Waiting state, patron cannot cancel');
795
796     C4::Reserves::ModReserveStatus( $item->itemnumber, 'T' );
797     $reply = C4::ILSDI::Services::CancelHold($cgi);
798     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Transfer state, patron cannot cancel');
799
800     C4::Reserves::ModReserve( {rank => 1, reserve_id => $reserve, branchcode => $library->branchcode} );
801     $cgi->param( item_id => $reserve );
802     $reply = C4::ILSDI::Services::CancelHold($cgi);
803     is( $reply->{code}, 'Canceled', 'If the patron is fine and reserve not waiting, Canceled should be returned and reserve canceled');
804
805     $schema->storage->txn_rollback;
806 };
807
808 subtest 'GetPatronInfo paginated loans' => sub {
809     plan tests => 7;
810
811     $schema->storage->txn_begin;
812
813     my $library = $builder->build_object({
814         class => 'Koha::Libraries',
815     });
816
817     my $item1 = $builder->build_sample_item({ library => $library->branchcode });
818     my $item2 = $builder->build_sample_item({ library => $library->branchcode });
819     my $item3 = $builder->build_sample_item({ library => $library->branchcode });
820     my $patron = $builder->build_object({
821         class => 'Koha::Patrons',
822         value => {
823             branchcode => $library->branchcode,
824         },
825     });
826     my $module = Test::MockModule->new('C4::Context');
827     $module->mock('userenv', sub { { branch => $library->branchcode } });
828     my $date_due = Koha::DateUtils::dt_from_string()->add(weeks => 2);
829     my $issue1 = C4::Circulation::AddIssue($patron->unblessed, $item1->barcode, $date_due);
830     my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
831     my $issue2 = C4::Circulation::AddIssue($patron->unblessed, $item2->barcode, $date_due);
832     my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
833     my $issue3 = C4::Circulation::AddIssue($patron->unblessed, $item3->barcode, $date_due);
834     my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
835
836     my $cgi = CGI->new;
837
838     $cgi->param( 'service', 'GetPatronInfo' );
839     $cgi->param( 'patron_id', $patron->borrowernumber );
840     $cgi->param( 'show_loans', '1' );
841     $cgi->param( 'loans_per_page', '2' );
842     $cgi->param( 'loans_page', '1' );
843     my $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
844
845     is($reply->{total_loans}, 3, 'total_loans == 3');
846     is(scalar @{ $reply->{loans}->{loan} }, 2, 'GetPatronInfo returned only 2 loans');
847     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item3->itemnumber);
848     is($reply->{loans}->{loan}->[1]->{itemnumber}, $item2->itemnumber);
849
850     $cgi->param( 'loans_page', '2' );
851     $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
852
853     is($reply->{total_loans}, 3, 'total_loans == 3');
854     is(scalar @{ $reply->{loans}->{loan} }, 1, 'GetPatronInfo returned only 1 loan');
855     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item1->itemnumber);
856
857     $schema->storage->txn_rollback;
858 };
859
860 subtest 'GetAvailability itemcallnumber' => sub {
861
862     plan tests => 4;
863
864     $schema->storage->txn_begin;
865
866     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
867
868     my $item1 = $builder->build_sample_item(
869         {
870             itemcallnumber => "callnumber",
871         }
872     );
873
874     my $item2 = $builder->build_sample_item( {} );
875
876     # Build the query
877     my $cgi = CGI->new;
878     $cgi->param( service => 'GetAvailability' );
879     $cgi->param( id      => $item1->itemnumber );
880     $cgi->param( id_type => 'item' );
881
882     # Output of GetAvailability is a string containing XML
883     my $reply = C4::ILSDI::Services::GetAvailability($cgi);
884
885     # Parse the output and get info
886     my $result_XML = XML::LibXML->load_xml( string => $reply );
887     my $reply_callnumber =
888       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
889
890     # Test the output
891     is( $reply_callnumber, $item1->itemcallnumber,
892         "GetAvailability item has an itemcallnumber tag" );
893
894     $cgi = CGI->new;
895     $cgi->param( service => 'GetAvailability' );
896     $cgi->param( id      => $item2->itemnumber );
897     $cgi->param( id_type => 'item' );
898     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
899     $result_XML = XML::LibXML->load_xml( string => $reply );
900     $reply_callnumber =
901       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
902     is( $reply_callnumber, '',
903         "As expected, GetAvailability item has no itemcallnumber tag" );
904
905     $cgi = CGI->new;
906     $cgi->param( service => 'GetAvailability' );
907     $cgi->param( id      => $item1->biblionumber );
908     $cgi->param( id_type => 'biblio' );
909     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
910     $result_XML = XML::LibXML->load_xml( string => $reply );
911     $reply_callnumber =
912       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
913     is( $reply_callnumber, $item1->itemcallnumber,
914         "GetAvailability biblio has an itemcallnumber tag" );
915
916     $cgi = CGI->new;
917     $cgi->param( service => 'GetAvailability' );
918     $cgi->param( id      => $item2->biblionumber );
919     $cgi->param( id_type => 'biblio' );
920     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
921     $result_XML = XML::LibXML->load_xml( string => $reply );
922     $reply_callnumber =
923       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
924     is( $reply_callnumber, '',
925         "As expected, GetAvailability biblio has no itemcallnumber tag" );
926
927     $schema->storage->txn_rollback;
928 };
929
930 subtest 'Bug 34893: ILS-DI can return the wrong patron for AuthenticatePatron' => sub {
931
932     plan tests => 2;
933
934     $schema->storage->txn_begin;
935
936     my $plain_password = 'tomasito';
937
938     $builder->build(
939         {
940             source => 'Borrower',
941             value  => {
942                 cardnumber => undef,
943             }
944         }
945     );
946
947     my $borrower0 = $builder->build(
948         {
949             source => 'Borrower',
950             value  => {
951                 cardnumber => "cardnumber1",
952                 userid     => undef,
953                 password   => Koha::AuthUtils::hash_password($plain_password),
954                 lastseen   => "2001-01-01 12:34:56"
955             }
956         }
957     );
958
959     my $borrower = $builder->build(
960         {
961             source => 'Borrower',
962             value  => {
963                 cardnumber => "cardnumber2",
964                 userid     => undef,
965                 password   => Koha::AuthUtils::hash_password($plain_password),
966                 lastseen   => "2001-01-01 12:34:56"
967             }
968         }
969     );
970
971     my $query = CGI->new;
972     $query->param( 'username', $borrower->{cardnumber} );
973     $query->param( 'password', $plain_password );
974
975     my $reply = C4::ILSDI::Services::AuthenticatePatron($query);
976     is( $reply->{id},   $borrower->{borrowernumber}, "userid and password - Patron authenticated" );
977     is( $reply->{code}, undef,                       "Error code undef" );
978     my $seen_patron = Koha::Patrons->find( { borrowernumber => $reply->{id} } );
979
980     $schema->storage->txn_rollback;
981 };