Bug 29484: Regression tests
[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 qw( AddIssue );
30
31 use Koha::AuthUtils;
32 use Koha::DateUtils qw( dt_from_string );
33
34 BEGIN {
35     use_ok('C4::ILSDI::Services', qw( AuthenticatePatron GetPatronInfo LookupPatron HoldTitle HoldItem GetRecords RenewLoan ));
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 => 9;
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   => 1,
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     Koha::CirculationRules->set_rule(
385         {
386             categorycode => $patron->{categorycode},
387             itemtype     => $item2->{itype},
388             branchcode   => $patron->{branchcode},
389             rule_name    => 'reservesallowed',
390             rule_value   => 0,
391         }
392     );
393
394     $query = CGI->new;
395     $query->param( 'patron_id', $patron->{borrowernumber});
396     $query->param( 'bib_id', $item2->biblionumber);
397     $query->param( 'item_id', $item2->itemnumber);
398
399     $reply = C4::ILSDI::Services::HoldItem( $query );
400     is( $reply->{code}, 'noReservesAllowed', "No reserves allowed" );
401
402     my $origin_branch = $builder->build(
403         {
404             source => 'Branch',
405             value  => {
406                 pickup_location => 1,
407             }
408         }
409     );
410
411     # Adding a holdable item.
412     my $item3 = $builder->build_sample_item(
413        {
414            barcode => '123456789',
415            library => $origin_branch->{branchcode}
416        });
417
418     my $item4 = $builder->build_sample_item(
419         {
420            biblionumber => $item3->biblionumber,
421            damaged => 1,
422            library => $origin_branch->{branchcode}
423        });
424
425     Koha::CirculationRules->set_rule(
426         {
427             categorycode => $patron->{categorycode},
428             itemtype     => $item3->{itype},
429             branchcode   => $patron->{branchcode},
430             rule_name    => 'reservesallowed',
431             rule_value   => 10,
432         }
433     );
434
435     $query = CGI->new;
436     $query->param( 'patron_id', $patron->{borrowernumber});
437     $query->param( 'bib_id', $item4->biblionumber);
438     $query->param( 'item_id', $item4->itemnumber);
439
440     $reply = C4::ILSDI::Services::HoldItem( $query );
441     is( $reply->{code}, 'damaged', "Item is damaged" );
442
443     my $module = Test::MockModule->new('C4::Context');
444     $module->mock('userenv', sub { { patron => $patron } });
445     my $issue = C4::Circulation::AddIssue($patron, $item3->barcode);
446     t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', '0' );
447
448     $query = CGI->new;
449     $query->param( 'patron_id', $patron->{borrowernumber});
450     $query->param( 'bib_id', $item3->biblionumber);
451     $query->param( 'item_id', $item3->itemnumber);
452     $query->param( 'pickup_location', $origin_branch->{branchcode});
453     $reply = C4::ILSDI::Services::HoldItem( $query );
454
455     is( $reply->{code}, 'alreadypossession', "Patron has issued same book" );
456     is( $reply->{pickup_location}, undef, "No reserve placed");
457
458     # Test Patron cannot reserve if expired and BlockExpiredPatronOpacActions
459     my $category = $builder->build({
460         source => 'Category',
461         value => { BlockExpiredPatronOpacActions => -1 }
462         });
463
464     my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
465
466     my $expired_borrowernumber = Koha::Patron->new({
467         firstname =>  'Expired',
468         surname => 'Patron',
469         categorycode => $category->{categorycode},
470         branchcode => $branch_1,
471         dateexpiry => '2000-01-01',
472     })->store->borrowernumber;
473
474     t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
475
476     my $item5 = $builder->build({
477         source => 'Item',
478         value => {
479             biblionumber => $biblio_with_no_item->biblionumber,
480             damaged => 0,
481         }
482     });
483
484     $query = CGI->new;
485     $query->param( 'patron_id', $expired_borrowernumber);
486     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
487     $query->param( 'item_id', $item5->{itemnumber});
488
489     $reply = C4::ILSDI::Services::HoldItem( $query );
490     is( $reply->{code}, 'PatronExpired', "Patron is expired" );
491
492     $schema->storage->txn_rollback;
493 };
494
495 subtest 'Holds test for branch transfer limits' => sub {
496
497     plan tests => 6;
498
499     $schema->storage->txn_begin;
500
501     # Test enforement of branch transfer limits
502     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
503     t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
504
505     my $patron = $builder->build({
506         source => 'Borrower',
507     });
508
509     my $origin_branch = $builder->build(
510         {
511             source => 'Branch',
512             value  => {
513                 pickup_location => 1,
514             }
515         }
516     );
517     my $pickup_branch = $builder->build(
518         {
519             source => 'Branch',
520             value  => {
521                 pickup_location => 1,
522             }
523         }
524     );
525
526     my $item = $builder->build_sample_item(
527         {
528             library => $origin_branch->{branchcode},
529         }
530     );
531
532     Koha::CirculationRules->set_rule(
533         {
534             categorycode => undef,
535             itemtype     => undef,
536             branchcode   => undef,
537             rule_name    => 'reservesallowed',
538             rule_value   => 99,
539         }
540     );
541
542     my $limit = Koha::Item::Transfer::Limit->new({
543         toBranch => $pickup_branch->{branchcode},
544         fromBranch => $item->holdingbranch,
545         itemtype => $item->effective_itemtype,
546     })->store();
547
548     my $query = CGI->new;
549     $query->param( 'pickup_location', $pickup_branch->{branchcode} );
550     $query->param( 'patron_id', $patron->{borrowernumber});
551     $query->param( 'bib_id', $item->biblionumber);
552     $query->param( 'item_id', $item->itemnumber);
553
554     my $reply = C4::ILSDI::Services::HoldItem( $query );
555     is( $reply->{code}, 'cannotBeTransferred', "Item hold, Item cannot be transferred" );
556
557     $reply = C4::ILSDI::Services::HoldTitle( $query );
558     is( $reply->{code}, 'cannotBeTransferred', "Record hold, Item cannot be transferred" );
559
560     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
561
562     $reply = C4::ILSDI::Services::HoldItem( $query );
563     is( $reply->{code}, undef, "Item hold, Item can be transferred" );
564     my $hold = Koha::Holds->search({ itemnumber => $item->itemnumber, borrowernumber => $patron->{borrowernumber} })->next;
565     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
566
567     Koha::Holds->search()->delete();
568
569     $reply = C4::ILSDI::Services::HoldTitle( $query );
570     is( $reply->{code}, undef, "Record hold, Item con be transferred" );
571     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber, borrowernumber => $patron->{borrowernumber} })->next;
572     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
573
574     $schema->storage->txn_rollback;
575 };
576
577 subtest 'Holds test with start_date and end_date' => sub {
578
579     plan tests => 8;
580
581     $schema->storage->txn_begin;
582
583     my $pickup_library = $builder->build_object(
584         {
585             class  => 'Koha::Libraries',
586             value  => {
587                 pickup_location => 1,
588             }
589         }
590     );
591
592     my $patron = $builder->build_object({
593         class => 'Koha::Patrons',
594     });
595
596     my $item = $builder->build_sample_item({ library => $pickup_library->branchcode });
597
598     Koha::CirculationRules->set_rule(
599         {
600             categorycode => undef,
601             itemtype     => undef,
602             branchcode   => undef,
603             rule_name    => 'reservesallowed',
604             rule_value   => 99,
605         }
606     );
607
608     my $query = CGI->new;
609     $query->param( 'pickup_location', $pickup_library->branchcode );
610     $query->param( 'patron_id', $patron->borrowernumber);
611     $query->param( 'bib_id', $item->biblionumber);
612     $query->param( 'item_id', $item->itemnumber);
613     $query->param( 'start_date', '2020-03-20');
614     $query->param( 'expiry_date', '2020-04-22');
615
616     my $reply = C4::ILSDI::Services::HoldItem( $query );
617     is ($reply->{pickup_location}, $pickup_library->branchname, "Item hold with date parameters was placed");
618     my $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
619     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
620     is( $hold->reservedate, '2020-03-20', "Item hold has correct start date" );
621     is( $hold->expirationdate, '2020-04-22', "Item hold has correct end date" );
622
623     $hold->delete();
624
625     $reply = C4::ILSDI::Services::HoldTitle( $query );
626     is ($reply->{pickup_location}, $pickup_library->branchname, "Record hold with date parameters was placed");
627     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
628     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
629     is( $hold->reservedate, '2020-03-20', "Record hold has correct start date" );
630     is( $hold->expirationdate, '2020-04-22', "Record hold has correct end date" );
631
632     $schema->storage->txn_rollback;
633 };
634
635 subtest 'GetRecords' => sub {
636
637     plan tests => 8;
638
639     $schema->storage->txn_begin;
640
641     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
642
643     my $branch1 = $builder->build({
644         source => 'Branch',
645     });
646     my $branch2 = $builder->build({
647         source => 'Branch',
648     });
649
650     my $item = $builder->build_sample_item(
651         {
652             library => $branch1->{branchcode},
653         }
654     );
655
656     my $patron = $builder->build({
657         source => 'Borrower',
658     });
659
660     my $issue = $builder->build({
661         source => 'Issue',
662         value => {
663             itemnumber => $item->itemnumber,
664         }
665     });
666
667     my $hold = $builder->build({
668         source => 'Reserve',
669         value => {
670             biblionumber => $item->biblionumber,
671         }
672     });
673
674     ModItemTransfer($item->itemnumber, $branch1->{branchcode}, $branch2->{branchcode}, 'Manual');
675
676     my $cgi = CGI->new;
677     $cgi->param(service => 'GetRecords');
678     $cgi->param(id => $item->biblionumber);
679
680     my $reply = C4::ILSDI::Services::GetRecords($cgi);
681
682     my $transfer = $item->get_transfer;
683     my $expected = {
684         datesent => $transfer->datesent,
685         frombranch => $transfer->frombranch,
686         tobranch => $transfer->tobranch,
687     };
688     is_deeply($reply->{record}->[0]->{items}->{item}->[0]->{transfer}, $expected,
689         'GetRecords returns transfer informations');
690
691     # Check informations exposed
692     my $reply_issue = $reply->{record}->[0]->{issues}->{issue}->[0];
693     is($reply_issue->{itemnumber}, $item->itemnumber, 'GetRecords has an issue tag');
694     is($reply_issue->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in issue tag');
695     is($reply_issue->{surname}, undef, 'GetRecords does not expose surname in issue tag');
696     is($reply_issue->{firstname}, undef, 'GetRecords does not expose firstname in issue tag');
697     is($reply_issue->{cardnumber}, undef, 'GetRecords does not expose cardnumber in issue tag');
698     my $reply_reserve = $reply->{record}->[0]->{reserves}->{reserve}->[0];
699     is($reply_reserve->{biblionumber}, $item->biblionumber, 'GetRecords has a reserve tag');
700     is($reply_reserve->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in reserve tag');
701
702     $schema->storage->txn_rollback;
703 };
704
705 subtest 'RenewHold' => sub {
706     plan tests => 4;
707
708     $schema->storage->txn_begin;
709
710     my $cgi    = CGI->new;
711     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
712     my $item   = $builder->build_sample_item;
713     $cgi->param( patron_id => $patron->borrowernumber );
714     $cgi->param( item_id   => $item->itemnumber );
715
716     t::lib::Mocks::mock_userenv( { patron => $patron } );    # For AddIssue
717     my $checkout = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode );
718
719     # Everything is ok
720     my $reply = C4::ILSDI::Services::RenewLoan($cgi);
721     is( exists $reply->{date_due}, 1, 'If the item is checked out, the date_due key should exist' );
722
723     # The item is not checked out
724     $checkout->delete;
725     $reply = C4::ILSDI::Services::RenewLoan($cgi);
726     is( $reply, undef, 'If the item is not checked out, we should not explode.');    # FIXME We should return an error code instead
727
728     # The item does not exist
729     $item->delete;
730     $reply = C4::ILSDI::Services::RenewLoan($cgi);
731     is( $reply->{code}, 'RecordNotFound', 'If the item does not exist, RecordNotFound should be returned');
732
733     $patron->delete;
734     $reply = C4::ILSDI::Services::RenewLoan($cgi);
735     is( $reply->{code}, 'PatronNotFound', 'If the patron does not exist, PatronNotFound should be returned');
736
737     $schema->storage->txn_rollback;
738 };
739
740 subtest 'GetPatronInfo paginated loans' => sub {
741     plan tests => 7;
742
743     $schema->storage->txn_begin;
744
745     my $library = $builder->build_object({
746         class => 'Koha::Libraries',
747     });
748
749     my $item1 = $builder->build_sample_item({ library => $library->branchcode });
750     my $item2 = $builder->build_sample_item({ library => $library->branchcode });
751     my $item3 = $builder->build_sample_item({ library => $library->branchcode });
752     my $patron = $builder->build_object({
753         class => 'Koha::Patrons',
754         value => {
755             branchcode => $library->branchcode,
756         },
757     });
758     my $module = Test::MockModule->new('C4::Context');
759     $module->mock('userenv', sub { { branch => $library->branchcode } });
760     my $date_due = Koha::DateUtils::dt_from_string()->add(weeks => 2);
761     my $issue1 = C4::Circulation::AddIssue($patron->unblessed, $item1->barcode, $date_due);
762     my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
763     my $issue2 = C4::Circulation::AddIssue($patron->unblessed, $item2->barcode, $date_due);
764     my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
765     my $issue3 = C4::Circulation::AddIssue($patron->unblessed, $item3->barcode, $date_due);
766     my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
767
768     my $cgi = CGI->new;
769
770     $cgi->param( 'service', 'GetPatronInfo' );
771     $cgi->param( 'patron_id', $patron->borrowernumber );
772     $cgi->param( 'show_loans', '1' );
773     $cgi->param( 'loans_per_page', '2' );
774     $cgi->param( 'loans_page', '1' );
775     my $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
776
777     is($reply->{total_loans}, 3, 'total_loans == 3');
778     is(scalar @{ $reply->{loans}->{loan} }, 2, 'GetPatronInfo returned only 2 loans');
779     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item3->itemnumber);
780     is($reply->{loans}->{loan}->[1]->{itemnumber}, $item2->itemnumber);
781
782     $cgi->param( 'loans_page', '2' );
783     $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
784
785     is($reply->{total_loans}, 3, 'total_loans == 3');
786     is(scalar @{ $reply->{loans}->{loan} }, 1, 'GetPatronInfo returned only 1 loan');
787     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item1->itemnumber);
788
789     $schema->storage->txn_rollback;
790 };