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