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