Bug 32496: Fix 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 => 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_object( { class => 'Koha::Patrons' } );
329
330     my $item = $builder->build_sample_item(
331         {
332             damaged => 1
333         }
334     );
335
336     my $query = CGI->new;
337     $query->param( 'patron_id', $patron->borrowernumber);
338     $query->param( 'bib_id', $item->biblionumber);
339
340     my $reply = C4::ILSDI::Services::HoldTitle( $query );
341     is( $reply->{code}, 'damaged', "Item damaged" );
342
343     $item->damaged(0)->store;
344
345     my $hold = $builder->build({
346         source => 'Reserve',
347         value => {
348             borrowernumber => $patron->borrowernumber,
349             biblionumber => $item->biblionumber,
350             itemnumber => $item->itemnumber
351         }
352     });
353
354     $reply = C4::ILSDI::Services::HoldTitle( $query );
355     is( $reply->{code}, 'itemAlreadyOnHold', "Item already on hold" );
356
357     my $biblio_with_no_item = $builder->build_sample_biblio;
358
359     $query = CGI->new;
360     $query->param( 'patron_id', $patron->borrowernumber);
361     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
362
363     $reply = C4::ILSDI::Services::HoldTitle( $query );
364     is( $reply->{code}, 'NoItems', 'Biblio has no item' );
365
366     my $item2 = $builder->build_sample_item(
367         {
368             damaged => 0,
369         }
370     );
371
372     t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' );
373     Koha::CirculationRules->set_rule(
374         {
375             categorycode => $patron->categorycode,
376             itemtype     => $item2->{itype},
377             branchcode   => $patron->branchcode,
378             rule_name    => 'reservesallowed',
379             rule_value   => 1,
380         }
381     );
382
383     $query = CGI->new;
384     $query->param( 'patron_id', $patron->borrowernumber);
385     $query->param( 'bib_id', $item2->biblionumber);
386     $query->param( 'item_id', $item2->itemnumber);
387
388     $reply = C4::ILSDI::Services::HoldItem( $query );
389     is( $reply->{code}, 'tooManyReserves', "Too many reserves" );
390
391     Koha::CirculationRules->set_rule(
392         {
393             categorycode => $patron->categorycode,
394             itemtype     => $item2->{itype},
395             branchcode   => $patron->branchcode,
396             rule_name    => 'reservesallowed',
397             rule_value   => 0,
398         }
399     );
400
401     $query = CGI->new;
402     $query->param( 'patron_id', $patron->borrowernumber);
403     $query->param( 'bib_id', $item2->biblionumber);
404     $query->param( 'item_id', $item2->itemnumber);
405
406     $reply = C4::ILSDI::Services::HoldItem( $query );
407     is( $reply->{code}, 'noReservesAllowed', "No reserves allowed" );
408
409     my $origin_branch = $builder->build(
410         {
411             source => 'Branch',
412             value  => {
413                 pickup_location => 1,
414             }
415         }
416     );
417
418     # Adding a holdable item.
419     my $item3 = $builder->build_sample_item(
420        {
421            barcode => '123456789',
422            library => $origin_branch->{branchcode}
423        });
424
425     my $item4 = $builder->build_sample_item(
426         {
427            biblionumber => $item3->biblionumber,
428            damaged => 1,
429            library => $origin_branch->{branchcode}
430        });
431
432     Koha::CirculationRules->set_rule(
433         {
434             categorycode => $patron->categorycode,
435             itemtype     => $item3->{itype},
436             branchcode   => $patron->branchcode,
437             rule_name    => 'reservesallowed',
438             rule_value   => 10,
439         }
440     );
441
442     $query = CGI->new;
443     $query->param( 'patron_id', $patron->borrowernumber);
444     $query->param( 'bib_id', $item4->biblionumber);
445     $query->param( 'item_id', $item4->itemnumber);
446
447     $reply = C4::ILSDI::Services::HoldItem( $query );
448     is( $reply->{code}, 'damaged', "Item is damaged" );
449
450     my $module = Test::MockModule->new('C4::Context');
451     $module->mock('userenv', sub { { patron => $patron->unblessed } });
452     my $issue = C4::Circulation::AddIssue($patron, $item3->barcode);
453     t::lib::Mocks::mock_preference( 'AllowHoldsOnPatronsPossessions', '0' );
454
455     $query = CGI->new;
456     $query->param( 'patron_id', $patron->borrowernumber);
457     $query->param( 'bib_id', $item3->biblionumber);
458     $query->param( 'item_id', $item3->itemnumber);
459     $query->param( 'pickup_location', $origin_branch->{branchcode});
460     $reply = C4::ILSDI::Services::HoldItem( $query );
461
462     is( $reply->{code}, 'alreadypossession', "Patron has issued same book" );
463     is( $reply->{pickup_location}, undef, "No reserve placed");
464
465     # Test Patron cannot reserve if expired and BlockExpiredPatronOpacActions
466     my $category = $builder->build({
467         source => 'Category',
468         value => { BlockExpiredPatronOpacActions => -1 }
469         });
470
471     my $branch_1 = $builder->build({ source => 'Branch' })->{ branchcode };
472
473     my $expired_borrowernumber = Koha::Patron->new({
474         firstname =>  'Expired',
475         surname => 'Patron',
476         categorycode => $category->{categorycode},
477         branchcode => $branch_1,
478         dateexpiry => '2000-01-01',
479     })->store->borrowernumber;
480
481     t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
482
483     my $item5 = $builder->build({
484         source => 'Item',
485         value => {
486             biblionumber => $biblio_with_no_item->biblionumber,
487             damaged => 0,
488         }
489     });
490
491     $query = CGI->new;
492     $query->param( 'patron_id', $expired_borrowernumber);
493     $query->param( 'bib_id', $biblio_with_no_item->biblionumber);
494     $query->param( 'item_id', $item5->{itemnumber});
495
496     $reply = C4::ILSDI::Services::HoldItem( $query );
497     is( $reply->{code}, 'PatronExpired', "Patron is expired" );
498
499     $schema->storage->txn_rollback;
500 };
501
502 subtest 'Holds test for branch transfer limits' => sub {
503
504     plan tests => 6;
505
506     $schema->storage->txn_begin;
507
508     # Test enforement of branch transfer limits
509     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
510     t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
511
512     my $patron = $builder->build_object({
513         class => 'Koha::Patrons',
514     });
515
516     my $origin_branch = $builder->build(
517         {
518             source => 'Branch',
519             value  => {
520                 pickup_location => 1,
521             }
522         }
523     );
524     my $pickup_branch = $builder->build(
525         {
526             source => 'Branch',
527             value  => {
528                 pickup_location => 1,
529             }
530         }
531     );
532
533     my $item = $builder->build_sample_item(
534         {
535             library => $origin_branch->{branchcode},
536         }
537     );
538
539     Koha::CirculationRules->set_rule(
540         {
541             categorycode => undef,
542             itemtype     => undef,
543             branchcode   => undef,
544             rule_name    => 'reservesallowed',
545             rule_value   => 99,
546         }
547     );
548
549     my $limit = Koha::Item::Transfer::Limit->new({
550         toBranch => $pickup_branch->{branchcode},
551         fromBranch => $item->holdingbranch,
552         itemtype => $item->effective_itemtype,
553     })->store();
554
555     my $query = CGI->new;
556     $query->param( 'pickup_location', $pickup_branch->{branchcode} );
557     $query->param( 'patron_id', $patron->borrowernumber);
558     $query->param( 'bib_id', $item->biblionumber);
559     $query->param( 'item_id', $item->itemnumber);
560
561     my $reply = C4::ILSDI::Services::HoldItem( $query );
562     is( $reply->{code}, 'cannotBeTransferred', "Item hold, Item cannot be transferred" );
563
564     $reply = C4::ILSDI::Services::HoldTitle( $query );
565     is( $reply->{code}, 'cannotBeTransferred', "Record hold, Item cannot be transferred" );
566
567     t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
568
569     $reply = C4::ILSDI::Services::HoldItem( $query );
570     is( $reply->{code}, undef, "Item hold, Item can be transferred" );
571     my $hold = Koha::Holds->search({ itemnumber => $item->itemnumber, borrowernumber => $patron->borrowernumber })->next;
572     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
573
574     Koha::Holds->search()->delete();
575
576     $reply = C4::ILSDI::Services::HoldTitle( $query );
577     is( $reply->{code}, undef, "Record hold, Item con be transferred" );
578     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber, borrowernumber => $patron->borrowernumber })->next;
579     is( $hold->branchcode, $pickup_branch->{branchcode}, 'The library id is correctly set' );
580
581     $schema->storage->txn_rollback;
582 };
583
584 subtest 'Holds test with start_date and end_date' => sub {
585
586     plan tests => 8;
587
588     $schema->storage->txn_begin;
589
590     my $pickup_library = $builder->build_object(
591         {
592             class  => 'Koha::Libraries',
593             value  => {
594                 pickup_location => 1,
595             }
596         }
597     );
598
599     my $patron = $builder->build_object({
600         class => 'Koha::Patrons',
601     });
602
603     my $item = $builder->build_sample_item({ library => $pickup_library->branchcode });
604
605     Koha::CirculationRules->set_rule(
606         {
607             categorycode => undef,
608             itemtype     => undef,
609             branchcode   => undef,
610             rule_name    => 'reservesallowed',
611             rule_value   => 99,
612         }
613     );
614
615     my $query = CGI->new;
616     $query->param( 'pickup_location', $pickup_library->branchcode );
617     $query->param( 'patron_id', $patron->borrowernumber);
618     $query->param( 'bib_id', $item->biblionumber);
619     $query->param( 'item_id', $item->itemnumber);
620     $query->param( 'start_date', '2020-03-20');
621     $query->param( 'expiry_date', '2020-04-22');
622
623     my $reply = C4::ILSDI::Services::HoldItem( $query );
624     is ($reply->{pickup_location}, $pickup_library->branchname, "Item hold with date parameters was placed");
625     my $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
626     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
627     is( $hold->reservedate, '2020-03-20', "Item hold has correct start date" );
628     is( $hold->expirationdate, '2020-04-22', "Item hold has correct end date" );
629
630     $hold->delete();
631
632     $reply = C4::ILSDI::Services::HoldTitle( $query );
633     is ($reply->{pickup_location}, $pickup_library->branchname, "Record hold with date parameters was placed");
634     $hold = Koha::Holds->search({ biblionumber => $item->biblionumber})->next();
635     is( $hold->biblionumber, $item->biblionumber, "correct biblionumber");
636     is( $hold->reservedate, '2020-03-20', "Record hold has correct start date" );
637     is( $hold->expirationdate, '2020-04-22', "Record hold has correct end date" );
638
639     $schema->storage->txn_rollback;
640 };
641
642 subtest 'GetRecords' => sub {
643
644     plan tests => 8;
645
646     $schema->storage->txn_begin;
647
648     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
649
650     my $branch1 = $builder->build({
651         source => 'Branch',
652     });
653     my $branch2 = $builder->build({
654         source => 'Branch',
655     });
656
657     my $item = $builder->build_sample_item(
658         {
659             library => $branch1->{branchcode},
660         }
661     );
662
663     my $patron = $builder->build({
664         source => 'Borrower',
665     });
666
667     my $issue = $builder->build({
668         source => 'Issue',
669         value => {
670             itemnumber => $item->itemnumber,
671         }
672     });
673
674     my $hold = $builder->build({
675         source => 'Reserve',
676         value => {
677             biblionumber => $item->biblionumber,
678         }
679     });
680
681     ModItemTransfer($item->itemnumber, $branch1->{branchcode}, $branch2->{branchcode}, 'Manual');
682
683     my $cgi = CGI->new;
684     $cgi->param(service => 'GetRecords');
685     $cgi->param(id => $item->biblionumber);
686
687     my $reply = C4::ILSDI::Services::GetRecords($cgi);
688
689     my $transfer = $item->get_transfer;
690     my $expected = {
691         datesent => $transfer->datesent,
692         frombranch => $transfer->frombranch,
693         tobranch => $transfer->tobranch,
694     };
695     is_deeply($reply->{record}->[0]->{items}->{item}->[0]->{transfer}, $expected,
696         'GetRecords returns transfer informations');
697
698     # Check informations exposed
699     my $reply_issue = $reply->{record}->[0]->{issues}->{issue}->[0];
700     is($reply_issue->{itemnumber}, $item->itemnumber, 'GetRecords has an issue tag');
701     is($reply_issue->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in issue tag');
702     is($reply_issue->{surname}, undef, 'GetRecords does not expose surname in issue tag');
703     is($reply_issue->{firstname}, undef, 'GetRecords does not expose firstname in issue tag');
704     is($reply_issue->{cardnumber}, undef, 'GetRecords does not expose cardnumber in issue tag');
705     my $reply_reserve = $reply->{record}->[0]->{reserves}->{reserve}->[0];
706     is($reply_reserve->{biblionumber}, $item->biblionumber, 'GetRecords has a reserve tag');
707     is($reply_reserve->{borrowernumber}, undef, 'GetRecords does not expose borrowernumber in reserve tag');
708
709     $schema->storage->txn_rollback;
710 };
711
712 subtest 'RenewHold' => sub {
713     plan tests => 4;
714
715     $schema->storage->txn_begin;
716
717     my $cgi    = CGI->new;
718     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
719     my $item   = $builder->build_sample_item;
720     $cgi->param( patron_id => $patron->borrowernumber );
721     $cgi->param( item_id   => $item->itemnumber );
722
723     t::lib::Mocks::mock_userenv( { patron => $patron } );    # For AddIssue
724     my $checkout = C4::Circulation::AddIssue( $patron, $item->barcode );
725
726     # Everything is ok
727     my $reply = C4::ILSDI::Services::RenewLoan($cgi);
728     is( exists $reply->{date_due}, 1, 'If the item is checked out, the date_due key should exist' );
729
730     # The item is not checked out
731     $checkout->delete;
732     $reply = C4::ILSDI::Services::RenewLoan($cgi);
733     is( $reply, undef, 'If the item is not checked out, we should not explode.');    # FIXME We should return an error code instead
734
735     # The item does not exist
736     $item->delete;
737     $reply = C4::ILSDI::Services::RenewLoan($cgi);
738     is( $reply->{code}, 'RecordNotFound', 'If the item does not exist, RecordNotFound should be returned');
739
740     $patron->delete;
741     $reply = C4::ILSDI::Services::RenewLoan($cgi);
742     is( $reply->{code}, 'PatronNotFound', 'If the patron does not exist, PatronNotFound should be returned');
743
744     $schema->storage->txn_rollback;
745 };
746
747 subtest 'CancelHold' => sub {
748     plan tests => 4;
749
750     $schema->storage->txn_begin;
751
752     my $cgi = CGI->new;
753
754     my $library = $builder->build_object({
755         class => 'Koha::Libraries',
756     });
757
758     my $patron = $builder->build_object( { class => 'Koha::Patrons',
759                                            value => {
760                                                branchcode => $library->branchcode,
761                                            },
762                                          } );
763
764     my $patron2 = $builder->build_object( { class => 'Koha::Patrons',
765                                            value => {
766                                                branchcode => $library->branchcode,
767                                            },
768                                          } );
769
770     my $item = $builder->build_sample_item({ library => $library->branchcode });
771
772     my $reserve = C4::Reserves::AddReserve({branchcode => $library->branchcode,
773                                             borrowernumber => $patron->borrowernumber,
774                                             biblionumber => $item->biblionumber });
775
776     # Affecting the reserve sets it to a waiting state
777     C4::Reserves::ModReserveAffect( $item->itemnumber,
778                                     $patron->borrowernumber,
779                                     undef,
780                                     $reserve,
781                                    );
782
783     $cgi->param( patron_id => $patron2->borrowernumber );
784     $cgi->param( item_id   => $reserve );
785
786
787     my $reply = C4::ILSDI::Services::CancelHold($cgi);
788     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If the patron is wrong, BorrowerCannotCancelHold should be returned');
789
790     $cgi->param( patron_id => $patron->borrowernumber );
791     $reply = C4::ILSDI::Services::CancelHold($cgi);
792     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Waiting state, patron cannot cancel');
793
794     C4::Reserves::ModReserveStatus( $item->itemnumber, 'T' );
795     $reply = C4::ILSDI::Services::CancelHold($cgi);
796     is( $reply->{code}, 'BorrowerCannotCancelHold', 'If reserve in a Transfer state, patron cannot cancel');
797
798     C4::Reserves::ModReserve( {rank => 1, reserve_id => $reserve, branchcode => $library->branchcode} );
799     $cgi->param( item_id => $reserve );
800     $reply = C4::ILSDI::Services::CancelHold($cgi);
801     is( $reply->{code}, 'Canceled', 'If the patron is fine and reserve not waiting, Canceled should be returned and reserve canceled');
802
803     $schema->storage->txn_rollback;
804 };
805
806 subtest 'GetPatronInfo paginated loans' => sub {
807     plan tests => 7;
808
809     $schema->storage->txn_begin;
810
811     my $library = $builder->build_object({
812         class => 'Koha::Libraries',
813     });
814
815     my $item1 = $builder->build_sample_item({ library => $library->branchcode });
816     my $item2 = $builder->build_sample_item({ library => $library->branchcode });
817     my $item3 = $builder->build_sample_item({ library => $library->branchcode });
818     my $patron = $builder->build_object({
819         class => 'Koha::Patrons',
820         value => {
821             branchcode => $library->branchcode,
822         },
823     });
824     my $module = Test::MockModule->new('C4::Context');
825     $module->mock('userenv', sub { { branch => $library->branchcode } });
826     my $date_due = Koha::DateUtils::dt_from_string()->add(weeks => 2);
827     my $issue1 = C4::Circulation::AddIssue($patron, $item1->barcode, $date_due);
828     my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due );
829     my $issue2 = C4::Circulation::AddIssue($patron, $item2->barcode, $date_due);
830     my $date_due2 = Koha::DateUtils::dt_from_string( $issue2->date_due );
831     my $issue3 = C4::Circulation::AddIssue($patron, $item3->barcode, $date_due);
832     my $date_due3 = Koha::DateUtils::dt_from_string( $issue3->date_due );
833
834     my $cgi = CGI->new;
835
836     $cgi->param( 'service', 'GetPatronInfo' );
837     $cgi->param( 'patron_id', $patron->borrowernumber );
838     $cgi->param( 'show_loans', '1' );
839     $cgi->param( 'loans_per_page', '2' );
840     $cgi->param( 'loans_page', '1' );
841     my $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
842
843     is($reply->{total_loans}, 3, 'total_loans == 3');
844     is(scalar @{ $reply->{loans}->{loan} }, 2, 'GetPatronInfo returned only 2 loans');
845     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item3->itemnumber);
846     is($reply->{loans}->{loan}->[1]->{itemnumber}, $item2->itemnumber);
847
848     $cgi->param( 'loans_page', '2' );
849     $reply = C4::ILSDI::Services::GetPatronInfo($cgi);
850
851     is($reply->{total_loans}, 3, 'total_loans == 3');
852     is(scalar @{ $reply->{loans}->{loan} }, 1, 'GetPatronInfo returned only 1 loan');
853     is($reply->{loans}->{loan}->[0]->{itemnumber}, $item1->itemnumber);
854
855     $schema->storage->txn_rollback;
856 };
857
858 subtest 'GetAvailability itemcallnumber' => sub {
859
860     plan tests => 4;
861
862     $schema->storage->txn_begin;
863
864     t::lib::Mocks::mock_preference( 'ILS-DI', 1 );
865
866     my $item1 = $builder->build_sample_item(
867         {
868             itemcallnumber => "callnumber",
869         }
870     );
871
872     my $item2 = $builder->build_sample_item( {} );
873
874     # Build the query
875     my $cgi = CGI->new;
876     $cgi->param( service => 'GetAvailability' );
877     $cgi->param( id      => $item1->itemnumber );
878     $cgi->param( id_type => 'item' );
879
880     # Output of GetAvailability is a string containing XML
881     my $reply = C4::ILSDI::Services::GetAvailability($cgi);
882
883     # Parse the output and get info
884     my $result_XML = XML::LibXML->load_xml( string => $reply );
885     my $reply_callnumber =
886       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
887
888     # Test the output
889     is( $reply_callnumber, $item1->itemcallnumber,
890         "GetAvailability item has an itemcallnumber tag" );
891
892     $cgi = CGI->new;
893     $cgi->param( service => 'GetAvailability' );
894     $cgi->param( id      => $item2->itemnumber );
895     $cgi->param( id_type => 'item' );
896     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
897     $result_XML = XML::LibXML->load_xml( string => $reply );
898     $reply_callnumber =
899       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
900     is( $reply_callnumber, '',
901         "As expected, GetAvailability item has no itemcallnumber tag" );
902
903     $cgi = CGI->new;
904     $cgi->param( service => 'GetAvailability' );
905     $cgi->param( id      => $item1->biblionumber );
906     $cgi->param( id_type => 'biblio' );
907     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
908     $result_XML = XML::LibXML->load_xml( string => $reply );
909     $reply_callnumber =
910       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
911     is( $reply_callnumber, $item1->itemcallnumber,
912         "GetAvailability biblio has an itemcallnumber tag" );
913
914     $cgi = CGI->new;
915     $cgi->param( service => 'GetAvailability' );
916     $cgi->param( id      => $item2->biblionumber );
917     $cgi->param( id_type => 'biblio' );
918     $reply      = C4::ILSDI::Services::GetAvailability($cgi);
919     $result_XML = XML::LibXML->load_xml( string => $reply );
920     $reply_callnumber =
921       $result_XML->findnodes('//dlf:itemcallnumber')->to_literal();
922     is( $reply_callnumber, '',
923         "As expected, GetAvailability biblio has no itemcallnumber tag" );
924
925     $schema->storage->txn_rollback;
926 };