Bug 33580: Bring back ability to mark item as seen via SIP2 item information request
[koha.git] / t / db_dependent / SIP / Message.t
1 #!/usr/bin/perl
2
3 # Tests for SIP::Sip::MsgType
4 # Please help to extend it!
5
6 # This file is part of Koha.
7 #
8 # Copyright 2016 Rijksmuseum
9 #
10 # Koha is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Koha is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22
23 use Modern::Perl;
24 use Test::More tests => 16;
25 use Test::Exception;
26 use Test::MockObject;
27 use Test::MockModule;
28 use Test::Warn;
29
30 use t::lib::Mocks;
31 use t::lib::TestBuilder;
32
33 use C4::Reserves qw( AddReserve );
34 use C4::Circulation qw( AddIssue AddReturn );
35 use Koha::Database;
36 use Koha::AuthUtils qw(hash_password);
37 use Koha::DateUtils qw( dt_from_string output_pref );
38 use Koha::Items;
39 use Koha::Checkouts;
40 use Koha::Old::Checkouts;
41 use Koha::Patrons;
42 use Koha::Holds;
43
44 use C4::SIP::ILS;
45 use C4::SIP::ILS::Patron;
46 use C4::SIP::Sip qw(write_msg);
47 use C4::SIP::Sip::Constants qw(:all);
48 use C4::SIP::Sip::MsgType;
49
50 use constant PATRON_PW => 'do_not_ever_use_this_one';
51
52 # START testing
53 subtest 'Testing Patron Status Request V2' => sub {
54     my $schema = Koha::Database->new->schema;
55     $schema->storage->txn_begin;
56     plan tests => 13;
57     $C4::SIP::Sip::protocol_version = 2;
58     test_request_patron_status_v2();
59     $schema->storage->txn_rollback;
60 };
61
62 subtest 'Testing Patron Info Request V2' => sub {
63     my $schema = Koha::Database->new->schema;
64     $schema->storage->txn_begin;
65     plan tests => 24;
66     $C4::SIP::Sip::protocol_version = 2;
67     test_request_patron_info_v2();
68     $schema->storage->txn_rollback;
69 };
70
71 subtest 'Checkout V2' => sub {
72     my $schema = Koha::Database->new->schema;
73     $schema->storage->txn_begin;
74     plan tests => 5;
75     $C4::SIP::Sip::protocol_version = 2;
76     test_checkout_v2();
77     $schema->storage->txn_rollback;
78 };
79
80 subtest 'Test checkout desensitize' => sub {
81     my $schema = Koha::Database->new->schema;
82     $schema->storage->txn_begin;
83     plan tests => 6;
84     $C4::SIP::Sip::protocol_version = 2;
85     test_checkout_desensitize();
86     $schema->storage->txn_rollback;
87 };
88
89 subtest 'Test renew desensitize' => sub {
90     my $schema = Koha::Database->new->schema;
91     $schema->storage->txn_begin;
92     plan tests => 6;
93     $C4::SIP::Sip::protocol_version = 2;
94     test_renew_desensitize();
95     $schema->storage->txn_rollback;
96 };
97
98 subtest 'Checkin V2' => sub {
99     my $schema = Koha::Database->new->schema;
100     $schema->storage->txn_begin;
101     plan tests => 39;
102     $C4::SIP::Sip::protocol_version = 2;
103     test_checkin_v2();
104     $schema->storage->txn_rollback;
105 };
106
107 subtest 'Test hold_patron_bcode' => sub {
108     my $schema = Koha::Database->new->schema;
109     $schema->storage->txn_begin;
110     plan tests => 2;
111     $C4::SIP::Sip::protocol_version = 2;
112     test_hold_patron_bcode();
113     $schema->storage->txn_rollback;
114 };
115
116 subtest 'UseLocationAsAQInSIP syspref tests' => sub {
117     plan tests => 2;
118
119     my $schema = Koha::Database->new->schema;
120     $schema->storage->txn_begin;
121
122     my $builder = t::lib::TestBuilder->new();
123
124     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
125
126     t::lib::Mocks::mock_preference('UseLocationAsAQInSIP', 0);
127
128      my $item = $builder->build_sample_item(
129         {
130             damaged       => 0,
131             withdrawn     => 0,
132             itemlost      => 0,
133             restricted    => 0,
134             homebranch    => $branchcode,
135             holdingbranch => $branchcode,
136             permanent_location => "PERMANENT_LOCATION"
137         }
138     );
139
140     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
141     is( $sip_item->permanent_location, $branchcode, "When UseLocationAsAQInSIP is not set SIP item has permanent_location set to value of homebranch" );
142
143     t::lib::Mocks::mock_preference('UseLocationAsAQInSIP', 1);
144
145     $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
146     is( $sip_item->permanent_location, "PERMANENT_LOCATION", "When UseLocationAsAQInSIP is set SIP item has permanent_location set to value of item permanent_location" );
147
148     $schema->storage->txn_rollback;
149 };
150
151 subtest 'hold_patron_name() tests' => sub {
152
153     plan tests => 3;
154
155     my $schema = Koha::Database->new->schema;
156     $schema->storage->txn_begin;
157
158     my $builder = t::lib::TestBuilder->new();
159
160     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
161     my ( $response, $findpatron );
162     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
163
164     my $item = $builder->build_sample_item(
165         {
166             damaged       => 0,
167             withdrawn     => 0,
168             itemlost      => 0,
169             restricted    => 0,
170             homebranch    => $branchcode,
171             holdingbranch => $branchcode
172         }
173     );
174
175     my $server = { ils => $mocks->{ils} };
176     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
177
178     is( $sip_item->hold_patron_name, q{}, "SIP item with no hold returns empty string for patron name" );
179
180     my $resp = C4::SIP::Sip::maybe_add( FID_CALL_NUMBER, $sip_item->hold_patron_name, $server );
181     is( $resp, q{}, "maybe_add returns empty string for SIP item with no hold returns empty string" );
182
183     $resp = C4::SIP::Sip::maybe_add( FID_CALL_NUMBER, "0", $server );
184     is( $resp, q{CS0|}, "maybe_add will create the field of the string '0'" );
185
186     $schema->storage->txn_rollback;
187 };
188
189 subtest 'Lastseen response' => sub {
190
191     my $schema = Koha::Database->new->schema;
192     $schema->storage->txn_begin;
193
194     plan tests => 6;
195     my $builder = t::lib::TestBuilder->new();
196     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
197     my ( $response, $findpatron );
198     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
199     my $seen_patron = $builder->build({
200         source => 'Borrower',
201         value  => {
202             lastseen => '2001-01-01',
203             password => hash_password( PATRON_PW ),
204             branchcode => $branchcode,
205         },
206     });
207     my $cardnum = $seen_patron->{cardnumber};
208     my $sip_patron = C4::SIP::ILS::Patron->new( $cardnum );
209     $findpatron = $sip_patron;
210
211     my $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y         '.
212         FID_INST_ID. $branchcode. '|'.
213         FID_PATRON_ID. $cardnum. '|'.
214         FID_PATRON_PWD. PATRON_PW. '|';
215     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
216
217     my $server = { ils => $mocks->{ils} };
218     undef $response;
219     t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '' );
220     $msg->handle_patron_info( $server );
221
222     isnt( $response, undef, 'At least we got a response.' );
223     my $respcode = substr( $response, 0, 2 );
224     is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
225     $seen_patron = Koha::Patrons->find({ cardnumber => $seen_patron->{cardnumber} });
226     is( output_pref({str => $seen_patron->lastseen(), dateonly => 1}), output_pref({str => '2001-01-01', dateonly => 1}),'Last seen not updated if not tracking patrons');
227     undef $response;
228     t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '1' );
229     $msg->handle_patron_info( $server );
230
231     isnt( $response, undef, 'At least we got a response.' );
232     $respcode = substr( $response, 0, 2 );
233     is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
234     $seen_patron = Koha::Patrons->find({ cardnumber => $seen_patron->cardnumber() });
235     is( output_pref({str => $seen_patron->lastseen(), dateonly => 1}), output_pref({dt => dt_from_string(), dateonly => 1}),'Last seen updated if tracking patrons');
236     $schema->storage->txn_rollback;
237
238 };
239
240 subtest "Test patron_status_string" => sub {
241     my $schema = Koha::Database->new->schema;
242     $schema->storage->txn_begin;
243
244     plan tests => 9;
245
246     my $builder = t::lib::TestBuilder->new();
247     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
248     my $patron = $builder->build({
249         source => 'Borrower',
250         value  => {
251             branchcode => $branchcode,
252         },
253     });
254     my $sip_patron = C4::SIP::ILS::Patron->new( $patron->{cardnumber} );
255
256     t::lib::Mocks::mock_userenv({ branchcode => $branchcode });
257
258      my $item1 = $builder->build_sample_item(
259         {
260             damaged       => 0,
261             withdrawn     => 0,
262             itemlost      => 0,
263             restricted    => 0,
264             homebranch    => $branchcode,
265             holdingbranch => $branchcode,
266             permanent_location => "PERMANENT_LOCATION"
267         }
268     );
269      AddIssue( $patron, $item1->barcode );
270
271      my $item2 = $builder->build_sample_item(
272         {
273             damaged       => 0,
274             withdrawn     => 0,
275             itemlost      => 0,
276             restricted    => 0,
277             homebranch    => $branchcode,
278             holdingbranch => $branchcode,
279             permanent_location => "PERMANENT_LOCATION"
280         }
281     );
282     AddIssue( $patron, $item2->barcode );
283
284     is( Koha::Checkouts->search({ borrowernumber => $patron->{borrowernumber} })->count, 2, "Found 2 checkouts for this patron" );
285
286     $item1->itemlost(1)->store();
287     $item2->itemlost(2)->store();
288
289     is( Koha::Checkouts->search({ borrowernumber => $patron->{borrowernumber}, 'itemlost' => { '>', 0 } }, { join => 'item'} )->count, 2, "Found 2 lost checkouts for this patron" );
290
291     my $server->{account}->{lost_block_checkout} = undef;
292     my $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
293     is( substr($patron_status_string, 9, 1), q{ }, "lost_block_checkout = 0 does not block checkouts with 2 lost checkouts" );;
294
295     $server->{account}->{lost_block_checkout} = 0;
296     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
297     is( substr($patron_status_string, 9, 1), q{ }, "lost_block_checkout = 0 does not block checkouts with 2 lost checkouts" );;
298
299     $server->{account}->{lost_block_checkout} = 1;
300     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
301     is( substr($patron_status_string, 9, 1), q{Y}, "lost_block_checkout = 1 does block checkouts with 2 lost checkouts" );;
302
303     $server->{account}->{lost_block_checkout} = 2;
304     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
305     is( substr($patron_status_string, 9, 1), q{Y}, "lost_block_checkout = 2 does block checkouts with 2 lost checkouts" );;
306
307     $server->{account}->{lost_block_checkout} = 3;
308     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
309     is( substr($patron_status_string, 9, 1), q{ }, "lost_block_checkout = 3 does not block checkouts with 2 lost checkouts" );;
310
311     $server->{account}->{lost_block_checkout} = 2;
312     $server->{account}->{lost_block_checkout_value} = 2;
313     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
314     is( substr($patron_status_string, 9, 1), q{ }, "lost_block_checkout = 2, lost_block_checkout_value = 2 does not block checkouts with 2 lost checkouts where only 1 has itemlost = 2" );
315
316     $server->{account}->{lost_block_checkout} = 1;
317     $server->{account}->{lost_block_checkout_value} = 2;
318     $patron_status_string = C4::SIP::Sip::MsgType::patron_status_string( $sip_patron, $server );
319     is( substr($patron_status_string, 9, 1), q{Y}, "lost_block_checkout = 2, lost_block_checkout_value = 2 does block checkouts with 2 lost checkouts where only 1 has itemlost = 2" );
320
321     $schema->storage->txn_rollback;
322 };
323
324 subtest "Test build_additional_item_fields_string" => sub {
325     my $schema = Koha::Database->new->schema;
326     $schema->storage->txn_begin;
327
328     plan tests => 2;
329
330     my $builder = t::lib::TestBuilder->new();
331
332     my $item = $builder->build_sample_item;
333     my $ils_item = C4::SIP::ILS::Item->new( $item->barcode );
334
335     my $server = {};
336     $server->{account}->{item_field}->{code} = 'itemnumber';
337     $server->{account}->{item_field}->{field} = 'XY';
338     my $attribute_string = $ils_item->build_additional_item_fields_string( $server );
339     is( $attribute_string, "XY".$item->itemnumber."|", 'Attribute field generated correctly with single param' );
340
341     $server = {};
342     $server->{account}->{item_field}->[0]->{code} = 'itemnumber';
343     $server->{account}->{item_field}->[0]->{field} = 'XY';
344     $server->{account}->{item_field}->[1]->{code} = 'biblionumber';
345     $server->{account}->{item_field}->[1]->{field} = 'YZ';
346     $attribute_string = $ils_item->build_additional_item_fields_string( $server );
347     is( $attribute_string, sprintf("XY%s|YZ%s|", $item->itemnumber, $item->biblionumber), 'Attribute field generated correctly with multiple params' );
348
349     $schema->storage->txn_rollback;
350 };
351
352 subtest "Test build_custom_field_string" => sub {
353     my $schema = Koha::Database->new->schema;
354     $schema->storage->txn_begin;
355
356     plan tests => 2;
357
358     my $builder = t::lib::TestBuilder->new();
359
360     my $item = $builder->build_sample_item;
361     my $item_id = $item->id;
362     my $item_barcode = $item->barcode;
363     my $ils_item = C4::SIP::ILS::Item->new( $item->barcode );
364
365     my $server = {};
366     $server->{account}->{custom_item_field}->{field} = "XY";
367     $server->{account}->{custom_item_field}->{template} = "[% item.id %] [% item.barcode %], woo!";
368     my $attribute_string = $ils_item->build_additional_item_fields_string( $server );
369     is( $attribute_string, "XY$item_id $item_barcode, woo!|", 'Attribute field generated correctly with single param' );
370
371     $server = {};
372     $server->{account}->{custom_item_field}->[0]->{field} = "ZY";
373     $server->{account}->{custom_item_field}->[0]->{template} = "[% item.id %]!";
374     $server->{account}->{custom_item_field}->[1]->{field} = "ZX";
375     $server->{account}->{custom_item_field}->[1]->{template} = "[% item.barcode %]*";
376     $attribute_string = $ils_item->build_additional_item_fields_string( $server );
377     is( $attribute_string, sprintf("ZY%s!|ZX%s*|", $item_id, $item_barcode), 'Attribute field generated correctly with multiple params' );
378
379     $schema->storage->txn_rollback;
380 };
381
382 subtest "Test cr_item_field" => sub {
383     plan tests => 8;
384
385     my $builder = t::lib::TestBuilder->new();
386     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
387     my $branchcode2 = $builder->build({ source => 'Branch' })->{branchcode};
388     my ( $response, $findpatron );
389     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
390
391     # create some data
392     my $patron1 = $builder->build({
393         source => 'Borrower',
394         value  => {
395             password => hash_password( PATRON_PW ),
396         },
397     });
398     my $card1 = $patron1->{cardnumber};
399     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
400     $findpatron = $sip_patron1;
401     my $item_object = $builder->build_sample_item({
402         damaged => 0,
403         withdrawn => 0,
404         itemlost => 0,
405         restricted => 0,
406         homebranch => $branchcode,
407         holdingbranch => $branchcode,
408         datelastseen => '1900-01-01',
409     });
410
411     my $mockILS = $mocks->{ils};
412     my $server = { ils => $mockILS, account => {} };
413     $mockILS->mock( 'institution', sub { $branchcode; } );
414     $mockILS->mock( 'supports', sub { return; } );
415     $mockILS->mock( 'checkin', sub {
416         shift;
417         return C4::SIP::ILS->checkin(@_);
418     });
419     my $today = dt_from_string;
420
421     my $respcode;
422
423     # Not checked out, toggle option checked_in_ok
424     my $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
425         siprequestdate( $today->clone->add( days => 1) ) .
426         FID_INST_ID . $branchcode . '|'.
427         FID_ITEM_ID . $item_object->barcode . '|' .
428         FID_TERMINAL_PWD . 'ignored' . '|';
429     undef $response;
430     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
431
432     $server->{account}->{cr_item_field} = 'itemnumber';
433
434     $msg->handle_checkin( $server );
435
436     my $id = $item_object->id;
437     ok( $response =~ m/CR$id/, "Found correct CR field in response");
438
439     $siprequest = ITEM_INFORMATION . 'YYYYMMDDZZZZHHMMSS' .
440         FID_INST_ID . $branchcode . '|'.
441         FID_ITEM_ID . $item_object->barcode . '|' .
442         FID_TERMINAL_PWD . 'ignored' . '|';
443     undef $response;
444     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
445
446     $mockILS->mock( 'find_item', sub {
447         return C4::SIP::ILS::Item->new( $item_object->barcode );
448     });
449
450     $server->{account}->{cr_item_field} = 'itype';
451
452     $server->{account}->{seen_on_item_information} = '';
453     $msg->handle_item_information( $server );
454     $item_object->get_from_storage;
455     is( $item_object->datelastseen, "1900-01-01", "datelastseen remains unchanged" );
456
457     $item_object->update({ itemlost => 1, datelastseen => '1900-01-01' });
458     $server->{account}->{seen_on_item_information} = 'keep_lost';
459     $msg->handle_item_information( $server );
460     $item_object = Koha::Items->find( $item_object->id );
461     isnt( $item_object->datelastseen, "1900-01-01", "datelastseen updated" );
462     is( $item_object->itemlost, 1, "item remains lost" );
463
464     $item_object->update({ itemlost => 1, datelastseen => '1900-01-01' });
465     $server->{account}->{seen_on_item_information} = 'mark_found';
466     $msg->handle_item_information( $server );
467     $item_object = Koha::Items->find( $item_object->id );
468     isnt( $item_object->datelastseen, "1900-01-01", "datelastseen updated" );
469     is( $item_object->itemlost, 0, "item is no longer lost" );
470
471     my $itype = $item_object->itype;
472     ok( $response =~ m/CR$itype/, "Found correct CR field in response");
473
474     $server->{account}->{format_due_date} = 1;
475     t::lib::Mocks::mock_preference( 'dateFormat',  'sql' );
476     my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber, date_due => "1999-01-01 12:59:00" })->store;
477     $siprequest = ITEM_INFORMATION . 'YYYYMMDDZZZZHHMMSS' .
478         FID_INST_ID . $branchcode . '|'.
479         FID_ITEM_ID . $item_object->barcode . '|' .
480         FID_TERMINAL_PWD . 'ignored' . '|';
481     undef $response;
482     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
483     $msg->handle_item_information( $server );
484     ok( $response =~ m/AH1999-01-01 12:59/, "Found correct CR field in response");
485 };
486
487 subtest 'Patron info summary > 5 should not crash server' => sub {
488
489     my $schema = Koha::Database->new->schema;
490     $schema->storage->txn_begin;
491
492     plan tests => 22;
493     my $builder = t::lib::TestBuilder->new();
494     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
495     my ( $response, $findpatron );
496     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
497     my $seen_patron = $builder->build({
498         source => 'Borrower',
499         value  => {
500             lastseen => '2001-01-01',
501             password => hash_password( PATRON_PW ),
502             branchcode => $branchcode,
503         },
504     });
505     my $cardnum = $seen_patron->{cardnumber};
506     my $sip_patron = C4::SIP::ILS::Patron->new( $cardnum );
507     $findpatron = $sip_patron;
508
509     my @summaries = (
510         '          ',
511         'Y         ',
512         ' Y        ',
513         '  Y       ',
514         '   Y      ',
515         '    Y     ',
516         '     Y    ',
517         '      Y   ',
518         '       Y  ',
519         '        Y ',
520         '         Y',
521     );
522     for my $summary ( @summaries ) {
523         my $siprequest = PATRON_INFO . 'engYYYYMMDDZZZZHHMMSS' . $summary .
524             FID_INST_ID . $branchcode . '|' .
525             FID_PATRON_ID . $cardnum . '|' .
526             FID_PATRON_PWD . PATRON_PW . '|';
527         my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
528
529         my $server = { ils => $mocks->{ils} };
530         undef $response;
531         $msg->handle_patron_info( $server );
532
533         isnt( $response, undef, 'At least we got a response.' );
534         my $respcode = substr( $response, 0, 2 );
535         is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
536     }
537
538     $schema->storage->txn_rollback;
539 };
540
541 subtest 'SC status tests' => sub {
542
543     my $schema = Koha::Database->new->schema;
544     $schema->storage->txn_begin;
545
546     plan tests => 2;
547
548     my $builder = t::lib::TestBuilder->new();
549     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
550     my $sip_user = $builder->build_object({ class => "Koha::Patrons" });
551
552     my ( $response, $findpatron );
553     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
554     my $mockILS = $mocks->{ils};
555     $mockILS->mock( 'checkout_ok', sub {1} );
556     $mockILS->mock( 'checkin_ok', sub {1} );
557     $mockILS->mock( 'status_update_ok', sub {1} );
558     $mockILS->mock( 'offline_ok', sub {1} );
559     $mockILS->mock( 'supports', sub {1} );
560     my $server = Test::MockObject->new();
561     $server->mock( 'get_timeout', sub {'100'});
562     $server->{ils} = $mockILS;
563     $server->{sip_username} = $sip_user->userid;
564     $server->{account} = {};
565     $server->{policy} = { renewal =>1,retries=>'000'};
566
567     my $siprequest = SC_STATUS . '0' . '030' . '2.00';
568     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
569     $msg->handle_sc_status( $server );
570
571     like( $response, qr/98YYYYYY100000[0-9 ]{19}.00AO|BXYYYYYYYYYYYYYYYY|/, 'At least we got a response.' );
572
573     $sip_user->delete;
574
575     dies_ok{ $msg->handle_sc_status( $server ) } ,"Dies if sip user cannot be found";
576
577 };
578
579 # Here is room for some more subtests
580
581 # END of main code
582
583 sub test_request_patron_status_v2 {
584     my $builder = t::lib::TestBuilder->new();
585     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
586     my ( $response, $findpatron );
587     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
588
589     my $patron1 = $builder->build({
590         source => 'Borrower',
591         value  => {
592             password => hash_password( PATRON_PW ),
593         },
594     });
595     my $card1 = $patron1->{cardnumber};
596     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
597     $findpatron = $sip_patron1;
598
599     my $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
600         FID_INST_ID. $branchcode. '|'.
601         FID_PATRON_ID. $card1. '|'.
602         FID_PATRON_PWD. PATRON_PW. '|';
603     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
604
605     my $server = { ils => $mocks->{ils} };
606     undef $response;
607     $msg->handle_patron_status( $server );
608
609     isnt( $response, undef, 'At least we got a response.' );
610     my $respcode = substr( $response, 0, 2 );
611     is( $respcode, PATRON_STATUS_RESP, 'Response code fine' );
612
613     check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
614     check_field( $respcode, $response, FID_PATRON_ID, $card1, 'Verified patron id' );
615     check_field( $respcode, $response, FID_PERSONAL_NAME, $patron1->{surname}, 'Verified patron name', 'contains' );
616     check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
617     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
618     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'Verified non-empty screen message', 'regex' );
619
620     # Now, we pass a wrong password and verify CQ again
621     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
622         FID_INST_ID. $branchcode. '|'.
623         FID_PATRON_ID. $card1. '|'.
624         FID_PATRON_PWD. 'wrong_password'. '|';
625     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
626     undef $response;
627     $msg->handle_patron_status( $server );
628     $respcode = substr( $response, 0, 2 );
629     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'Verified code CQ for wrong pw' );
630
631     # Check empty password and verify CQ again
632     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
633         FID_INST_ID. $branchcode. '|'.
634         FID_PATRON_ID. $card1. '|'.
635         FID_PATRON_PWD. '|';
636     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
637     undef $response;
638     $msg->handle_patron_status( $server );
639     $respcode = substr( $response, 0, 2 );
640     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
641
642     # Finally, we send a wrong card number and check AE, BL
643     # This is done by removing the new patron first
644     Koha::Patrons->search({ cardnumber => $card1 })->delete;
645     undef $findpatron;
646     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
647         FID_INST_ID. $branchcode. '|'.
648         FID_PATRON_ID. $card1. '|'.
649         FID_PATRON_PWD. PATRON_PW. '|';
650     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
651     undef $response;
652     $msg->handle_patron_status( $server );
653     $respcode = substr( $response, 0, 2 );
654     check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
655     check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
656     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
657 }
658
659 sub test_request_patron_info_v2 {
660     my $builder = t::lib::TestBuilder->new();
661     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
662     my ( $response, $findpatron );
663     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
664
665     my $patron2 = $builder->build({
666         source => 'Borrower',
667         value  => {
668             password => hash_password( PATRON_PW ),
669         },
670     });
671     my $card = $patron2->{cardnumber};
672     my $sip_patron2 = C4::SIP::ILS::Patron->new( $card );
673     $findpatron = $sip_patron2;
674     my $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y         '.
675         FID_INST_ID. $branchcode. '|'.
676         FID_PATRON_ID. $card. '|'.
677         FID_PATRON_PWD. PATRON_PW. '|';
678     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
679
680     my $server = { ils => $mocks->{ils} };
681     undef $response;
682     $msg->handle_patron_info( $server );
683     isnt( $response, undef, 'At least we got a response.' );
684     my $respcode = substr( $response, 0, 2 );
685     is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
686
687     check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
688     check_field( $respcode, $response, FID_PATRON_ID, $card, 'Verified patron id' );
689     check_field( $respcode, $response, FID_PERSONAL_NAME, $patron2->{surname}, 'Verified patron name', 'contains' );
690     check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
691     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
692     check_field( $respcode, $response, FID_FEE_LMT, '.*', 'Checked existence of fee limit', 'regex' );
693     check_field( $respcode, $response, FID_HOME_ADDR, $patron2->{address}, 'Address in BD', 'contains' );
694     check_field( $respcode, $response, FID_EMAIL, $patron2->{email}, 'Verified email in BE' );
695     check_field( $respcode, $response, FID_HOME_PHONE, $patron2->{phone}, 'Verified home phone in BF' );
696     # No check for custom fields here (unofficial PB, PC and PI)
697     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'We have a screen msg', 'regex' );
698
699     # Test customized patron name in AE with same sip request
700     # This implicitly tests C4::SIP::ILS::Patron->name
701     $server->{account}->{ae_field_template} = "X[% patron.surname %]Y";
702     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
703     undef $response;
704     $msg->handle_patron_info( $server );
705     $respcode = substr( $response, 0, 2 );
706     check_field( $respcode, $response, FID_PERSONAL_NAME, 'X' . $patron2->{surname} . 'Y', 'Check customized patron name' );
707
708     undef $response;
709     $server->{account}->{hide_fields} = "BD,BE,BF,PB";
710     $msg->handle_patron_info( $server );
711     $respcode = substr( $response, 0, 2 );
712     check_field( $respcode, $response, FID_HOME_ADDR, undef, 'Home address successfully stripped from response' );
713     check_field( $respcode, $response, FID_EMAIL, undef, 'Email address successfully stripped from response' );
714     check_field( $respcode, $response, FID_HOME_PHONE, undef, 'Home phone successfully stripped from response' );
715     check_field( $respcode, $response, FID_PATRON_BIRTHDATE, undef, 'Date of birth successfully stripped from response' );
716     $server->{account}->{hide_fields} = "";
717
718     # Check empty password and verify CQ again
719     $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y         '.
720         FID_INST_ID. $branchcode. '|'.
721         FID_PATRON_ID. $card. '|'.
722         FID_PATRON_PWD. '|';
723     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
724     undef $response;
725     $msg->handle_patron_info( $server );
726     $respcode = substr( $response, 0, 2 );
727     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
728     # Test empty password is OK if account configured to allow
729     $server->{account}->{allow_empty_passwords} = 1;
730     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
731     undef $response;
732     $msg->handle_patron_info( $server );
733     $respcode = substr( $response, 0, 2 );
734     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'code CQ should be Y if empty AD allowed' );
735
736     t::lib::Mocks::mock_preference( 'FailedLoginAttempts', '1' );
737     my $patron = Koha::Patrons->find({ cardnumber => $card });
738     $patron->update({ login_attempts => 0 });
739     is( $patron->account_locked, 0, "Patron account not locked already" );
740     $msg->handle_patron_info( $server );
741     $patron = Koha::Patrons->find({ cardnumber => $card });
742     is( $patron->account_locked, 0, "Patron account is not locked by patron info messages with empty password" );
743
744     # Finally, we send a wrong card number
745     Koha::Patrons->search({ cardnumber => $card })->delete;
746     undef $findpatron;
747     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
748     undef $response;
749     $msg->handle_patron_info( $server );
750     $respcode = substr( $response, 0, 2 );
751     check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
752     check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
753     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
754 }
755
756 sub test_checkout_v2 {
757     my $builder = t::lib::TestBuilder->new();
758     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
759     my $branchcode2 = $builder->build({ source => 'Branch' })->{branchcode};
760     my ( $response, $findpatron );
761     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
762
763     # create some data
764     my $patron1 = $builder->build({
765         source => 'Borrower',
766         value  => {
767             password => hash_password( PATRON_PW ),
768         },
769     });
770     my $card1 = $patron1->{cardnumber};
771     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
772     $findpatron = $sip_patron1;
773     my $item_object = $builder->build_sample_item({
774         damaged => 0,
775         withdrawn => 0,
776         itemlost => 0,
777         restricted => 0,
778         homebranch => $branchcode,
779         holdingbranch => $branchcode,
780     });
781
782     my $mockILS = $mocks->{ils};
783     my $server = { ils => $mockILS, account => {} };
784     $mockILS->mock( 'institution', sub { $branchcode; } );
785     $mockILS->mock( 'supports', sub { return; } );
786     $mockILS->mock( 'checkout', sub {
787         shift;
788         return C4::SIP::ILS->checkout(@_);
789     });
790     my $today = dt_from_string;
791     t::lib::Mocks::mock_userenv({ branchcode => $branchcode, flags => 1 });
792     t::lib::Mocks::mock_preference( 'CheckPrevCheckout',  'hardyes' );
793
794     my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
795     my $return = AddReturn($item_object->barcode, $branchcode);
796
797     my $siprequest = CHECKOUT . 'YN' . siprequestdate($today) .
798     siprequestdate( $today->clone->add( days => 1) ) .
799     FID_INST_ID . $branchcode . '|'.
800     FID_PATRON_ID . $sip_patron1->id . '|' .
801     FID_ITEM_ID . $item_object->barcode . '|' .
802     FID_TERMINAL_PWD . 'ignored' . '|';
803     undef $response;
804
805     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
806     $server->{account}->{prevcheckout_block_checkout} = 1;
807     $msg->handle_checkout( $server );
808     my $respcode = substr( $response, 0, 2 );
809     check_field( $respcode, $response, FID_SCREEN_MSG, 'This item was previously checked out by you', 'Check screen msg', 'equals' );
810
811     is( Koha::Checkouts->search({ itemnumber => $item_object->id })->count, 0, "Item was not checked out (prevcheckout_block_checkout enabled)");
812
813     $server->{account}->{prevcheckout_block_checkout} = 0;
814     $msg->handle_checkout( $server );
815     $respcode = substr( $response, 0, 2 );
816     is( Koha::Checkouts->search({ itemnumber => $item_object->id })->count, 1, "Item was checked out (prevcheckout_block_checkout disabled)");
817
818     $msg->handle_checkout( $server );
819     ok( $response =~ m/AH\d{8}    \d{6}/, "Found AH field as timestamp in response");
820     $server->{account}->{format_due_date} = 1;
821     t::lib::Mocks::mock_preference( 'dateFormat',  'sql' );
822     undef $response;
823     $msg->handle_checkout( $server );
824     ok( $response =~ m/AH\d{4}-\d{2}-\d{2}/, "Found AH field as SQL date in response");
825
826 }
827
828 sub test_checkin_v2 {
829     my $builder = t::lib::TestBuilder->new();
830     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
831     my $branchcode2 = $builder->build({ source => 'Branch' })->{branchcode};
832     my ( $response, $findpatron );
833     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
834
835     # create some data
836     my $patron1 = $builder->build({
837         source => 'Borrower',
838         value  => {
839             password => hash_password( PATRON_PW ),
840         },
841     });
842     my $card1 = $patron1->{cardnumber};
843     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
844     $findpatron = $sip_patron1;
845     my $item_object = $builder->build_sample_item({
846         damaged => 0,
847         withdrawn => 0,
848         itemlost => 0,
849         restricted => 0,
850         homebranch => $branchcode,
851         holdingbranch => $branchcode,
852     });
853
854     my $mockILS = $mocks->{ils};
855     my $server = { ils => $mockILS, account => {} };
856     $mockILS->mock( 'institution', sub { $branchcode; } );
857     $mockILS->mock( 'supports', sub { return; } );
858     $mockILS->mock( 'checkin', sub {
859         shift;
860         return C4::SIP::ILS->checkin(@_);
861     });
862     my $today = dt_from_string;
863
864     # Checkin invalid barcode
865     Koha::Items->search({ barcode => 'not_to_be_found' })->delete;
866     my $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
867         siprequestdate( $today->clone->add( days => 1) ) .
868         FID_INST_ID . $branchcode . '|'.
869         FID_ITEM_ID . 'not_to_be_found' . '|' .
870         FID_TERMINAL_PWD . 'ignored' . '|';
871     undef $response;
872     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
873     warnings_like { $msg->handle_checkin( $server ); }
874         [ qr/No item 'not_to_be_found'/, qr/no item found in object to resensitize/ ],
875         'Checkin of invalid item with two warnings';
876     my $respcode = substr( $response, 0, 2 );
877     is( $respcode, CHECKIN_RESP, 'Response code fine' );
878     is( substr($response,2,1), '0', 'OK flag is false' );
879     is( substr($response,5,1), 'Y', 'Alert flag is set' );
880     check_field( $respcode, $response, FID_SCREEN_MSG, 'Invalid Item', 'Check screen msg', 'regex' );
881
882     # Not checked out, toggle option checked_in_ok
883     $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
884         siprequestdate( $today->clone->add( days => 1) ) .
885         FID_INST_ID . $branchcode . '|'.
886         FID_ITEM_ID . $item_object->barcode . '|' .
887         FID_TERMINAL_PWD . 'ignored' . '|';
888     undef $response;
889     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
890     $msg->handle_checkin( $server );
891     $respcode = substr( $response, 0, 2 );
892     is( substr($response,2,1), '0', 'OK flag is false when checking in an item that was not checked out' );
893     is( substr($response,5,1), 'Y', 'Alert flag is set' );
894     check_field( $respcode, $response, FID_SCREEN_MSG, 'not checked out', 'Check screen msg', 'regex' );
895     # Toggle option
896     $server->{account}->{checked_in_ok} = 1;
897     undef $response;
898     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
899     $msg->handle_checkin( $server );
900     is( substr($response,2,1), '1', 'OK flag is true now with checked_in_ok flag set when checking in an item that was not checked out' );
901     is( substr($response,5,1), 'N', 'Alert flag no longer set' );
902     check_field( $respcode, $response, FID_SCREEN_MSG, undef, 'No screen msg' );
903
904     # Move item to another holding branch to trigger CV of 04 with alert flag
905     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
906     $item_object->holdingbranch( $branchcode2 )->store();
907     undef $response;
908     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
909     $msg->handle_checkin( $server );
910     is( substr($response,5,1), 'Y', 'Alert flag is set with check_in_ok, item is checked in but needs transfer' );
911     check_field( $respcode, $response, FID_ALERT_TYPE, '04', 'Got FID_ALERT_TYPE (CV) field with value 04 ( needs transfer )' );
912     $item_object->holdingbranch( $branchcode )->store();
913     t::lib::Mocks::mock_preference( ' AllowReturnToBranch ', 'anywhere' );
914
915     $server->{account}->{cv_send_00_on_success} = 0;
916     undef $response;
917     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
918     $msg->handle_checkin( $server );
919     $respcode = substr( $response, 0, 2 );
920     check_field( $respcode, $response, FID_ALERT_TYPE, undef, 'No FID_ALERT_TYPE (CV) field' );
921     $server->{account}->{cv_send_00_on_success} = 1;
922     undef $response;
923     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
924     $msg->handle_checkin( $server );
925     $respcode = substr( $response, 0, 2 );
926     check_field( $respcode, $response, FID_ALERT_TYPE, '00', 'FID_ALERT_TYPE (CV) field is 00' );
927     $server->{account}->{checked_in_ok} = 0;
928     $server->{account}->{cv_send_00_on_success} = 0;
929
930     t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '1' );
931     $server->{account}->{checked_in_ok} = 0;
932     $server->{account}->{cv_triggers_alert} = 0;
933     undef $response;
934     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
935     $msg->handle_checkin( $server );
936     $respcode = substr( $response, 0, 2 );
937     is( substr( $response, 5, 1 ), 'Y', 'Checkin without CV triggers alert flag when cv_triggers_alert is off' );
938     t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '0' );
939     $server->{account}->{cv_triggers_alert} = 1;
940     undef $response;
941     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
942     $msg->handle_checkin( $server );
943     $respcode = substr( $response, 0, 2 );
944     is( substr( $response, 5, 1 ), 'N', 'Checkin without CV does not trigger alert flag when cv_triggers_alert is on' );
945     $server->{account}->{cv_triggers_alert} = 0;
946     t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '1' );
947
948     $server->{account}->{checked_in_ok} = 1;
949     $server->{account}->{ct_always_send} = 0;
950     undef $response;
951     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
952     $msg->handle_checkin( $server );
953     $respcode = substr( $response, 0, 2 );
954     check_field( $respcode, $response, FID_DESTINATION_LOCATION, undef, 'No FID_DESTINATION_LOCATION (CT) field' );
955     $server->{account}->{ct_always_send} = 1;
956     undef $response;
957     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
958     $msg->handle_checkin( $server );
959     $respcode = substr( $response, 0, 2 );
960     check_field( $respcode, $response, FID_DESTINATION_LOCATION, q{}, 'FID_DESTINATION_LOCATION (CT) field is empty but present' );
961     $server->{account}->{checked_in_ok} = 0;
962     $server->{account}->{ct_always_send} = 0;
963
964     # Checkin at wrong branch: issue item and switch branch, and checkin
965     my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
966     $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
967     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
968     undef $response;
969     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
970     $msg->handle_checkin( $server );
971     is( substr($response,2,1), '0', 'OK flag is false when we check in at the wrong branch and we do not allow it' );
972     is( substr($response,5,1), 'Y', 'Alert flag is set' );
973     check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed', 'Check screen msg' );
974     $branchcode = $item_object->homebranch;  # switch back
975     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
976
977     # Data corrupted: add same issue_id to old_issues
978     Koha::Old::Checkout->new({ issue_id => $issue->issue_id })->store;
979     undef $response;
980     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
981     warnings_like { $msg->handle_checkin( $server ); }
982         [ qr/Duplicate entry/, qr/data issues/ ],
983         'DBIx error on duplicate issue_id';
984     is( substr($response,2,1), '0', 'OK flag is false when we encounter data corruption in old_issues' );
985     is( substr($response,5,1), 'Y', 'Alert flag is set' );
986     check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed: data problem', 'Check screen msg' );
987
988     # Finally checkin without problems (remove duplicate id)
989     Koha::Old::Checkouts->search({ issue_id => $issue->issue_id })->delete;
990     undef $response;
991     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
992     $msg->handle_checkin( $server );
993     is( substr($response,2,1), '1', 'OK flag is true when we checkin after removing the duplicate' );
994     is( substr($response,5,1), 'N', 'Alert flag is not set' );
995     is( Koha::Checkouts->find( $issue->issue_id ), undef,
996         'Issue record is gone now' );
997
998     # Test account option no_holds_check that prevents items on hold from being checked in via SIP
999     $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
1000     is( Koha::Checkouts->search({ itemnumber => $item_object->id })->count, 1, "Item is checked out");
1001     Koha::Old::Checkouts->search({ issue_id => $issue->issue_id })->delete;
1002     $server->{account}->{holds_block_checkin} = 1;
1003     my $reserve_id = AddReserve({
1004         branchcode     => $branchcode,
1005         borrowernumber => $patron1->{borrowernumber},
1006         biblionumber   => $item_object->biblionumber,
1007         priority       => 1,
1008     });
1009     my $hold = Koha::Holds->find( $reserve_id );
1010     is( $hold->id, $reserve_id, "Hold was created successfully" );
1011     undef $response;
1012     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
1013     $msg->handle_checkin( $server );
1014     is( substr($response,2,1), '0', 'OK flag is false when we check in an item on hold and we do not allow it' );
1015     is( substr($response,5,1), 'Y', 'Alert flag is set' );
1016     is( Koha::Checkouts->search({ itemnumber => $item_object->id })->count, 1, "Item was not checked in");
1017     $hold->discard_changes;
1018     is( $hold->found, undef, "Hold was not marked as found by SIP when holds_block_checkin enabled");
1019     $server->{account}->{holds_block_checkin} = 0;
1020
1021     # Test account option holds_get_captured that automatically sets the hold as found for a hold and possibly sets it to in transit
1022     $server->{account}->{holds_get_captured} = 0;
1023     undef $response;
1024     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
1025     $msg->handle_checkin( $server );
1026     is( substr($response,2,1), '1', 'OK flag is true when we check in an item on hold and we allow it but do not capture it' );
1027     is( substr($response,5,1), 'Y', 'Alert flag is set' );
1028     is( Koha::Checkouts->search({ itemnumber => $item_object->id })->count, 0, "Item was checked in");
1029     $hold->discard_changes;
1030     is( $hold->found, undef, "Hold was not marked as found by SIP when holds_get_captured disabled");
1031     $hold->delete();
1032     $server->{account}->{holds_get_captured} = 1;
1033 }
1034
1035 sub test_hold_patron_bcode {
1036     my $builder = t::lib::TestBuilder->new();
1037     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
1038     my ( $response, $findpatron );
1039     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
1040
1041     my $item = $builder->build_sample_item(
1042         {
1043             library => $branchcode
1044         }
1045     );
1046
1047     my $server = { ils => $mocks->{ils} };
1048     my $sip_item = C4::SIP::ILS::Item->new( $item->barcode );
1049
1050     is( $sip_item->hold_patron_bcode, q{}, "SIP item with no hold returns empty string" );
1051
1052     my $resp = C4::SIP::Sip::maybe_add( FID_CALL_NUMBER, $sip_item->hold_patron_bcode, $server );
1053     is( $resp, q{}, "maybe_add returns empty string for SIP item with no hold returns empty string" );
1054 }
1055
1056 sub test_checkout_desensitize {
1057     my $builder = t::lib::TestBuilder->new();
1058     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
1059     my ( $response, $findpatron );
1060     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
1061
1062     # create some data
1063     my $patron1 = $builder->build({
1064         source => 'Borrower',
1065         value  => {
1066             password => hash_password( PATRON_PW ),
1067         },
1068     });
1069     my $card1 = $patron1->{cardnumber};
1070     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
1071     my $patron_category = $sip_patron1->ptype();
1072     $findpatron = $sip_patron1;
1073     my $item_object = $builder->build_sample_item({
1074         damaged => 0,
1075         withdrawn => 0,
1076         itemlost => 0,
1077         restricted => 0,
1078         homebranch => $branchcode,
1079         holdingbranch => $branchcode,
1080     });
1081     my $itemtype = $item_object->effective_itemtype;
1082
1083     my $mockILS = $mocks->{ils};
1084     my $server = { ils => $mockILS, account => {} };
1085     $mockILS->mock( 'institution', sub { $branchcode; } );
1086     $mockILS->mock( 'supports', sub { return; } );
1087     $mockILS->mock( 'checkout', sub {
1088         shift;
1089         return C4::SIP::ILS->checkout(@_);
1090     });
1091     my $today = dt_from_string;
1092     t::lib::Mocks::mock_userenv({ branchcode => $branchcode, flags => 1 });
1093     t::lib::Mocks::mock_preference( 'CheckPrevCheckout',  'hardyes' );
1094
1095     my $siprequest = CHECKOUT . 'YN' . siprequestdate($today) .
1096     siprequestdate( $today->clone->add( days => 1) ) .
1097     FID_INST_ID . $branchcode . '|'.
1098     FID_PATRON_ID . $sip_patron1->id . '|' .
1099     FID_ITEM_ID . $item_object->barcode . '|' .
1100     FID_TERMINAL_PWD . 'ignored' . '|';
1101
1102     undef $response;
1103     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
1104     $server->{account}->{inhouse_patron_categories} = "A,$patron_category,Z";
1105     $msg->handle_checkout( $server );
1106     my $respcode = substr( $response, 5, 1 );
1107     is( $respcode, 'N', "Desensitize flag was not set for patron category in inhouse_patron_categories" );
1108
1109     undef $response;
1110     $server->{account}->{inhouse_patron_categories} = "A,B,C";
1111     $msg->handle_checkout( $server );
1112     $respcode = substr( $response, 5, 1 );
1113     is( $respcode, 'Y', "Desensitize flag was set for patron category not in inhouse_patron_categories" );
1114
1115     undef $response;
1116     $server->{account}->{inhouse_patron_categories} = "";
1117     $msg->handle_checkout( $server );
1118     $respcode = substr( $response, 5, 1 );
1119     is( $respcode, 'Y', "Desensitize flag was set for empty inhouse_patron_categories" );
1120
1121     $server->{account}->{inhouse_patron_categories} = "";
1122
1123     undef $response;
1124     $server->{account}->{inhouse_item_types} = "A,$itemtype,Z";
1125     $msg->handle_checkout( $server );
1126     $respcode = substr( $response, 5, 1 );
1127     is( $respcode, 'N', "Desensitize flag was not set for itemtype in inhouse_item_types" );
1128
1129     undef $response;
1130     $server->{account}->{inhouse_item_types} = "A,B,C";
1131     $msg->handle_checkout( $server );
1132     $respcode = substr( $response, 5, 1 );
1133     is( $respcode, 'Y', "Desensitize flag was set for item type not in inhouse_item_types" );
1134
1135     undef $response;
1136     $server->{account}->{inhouse_item_types} = "";
1137     $msg->handle_checkout( $server );
1138     $respcode = substr( $response, 5, 1 );
1139     is( $respcode, 'Y', "Desensitize flag was set for empty inhouse_item_types" );
1140 }
1141
1142 sub test_renew_desensitize {
1143     my $builder = t::lib::TestBuilder->new();
1144     my $branchcode  = $builder->build({ source => 'Branch' })->{branchcode};
1145     my ( $response, $findpatron );
1146     my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
1147
1148     # create some data
1149     my $patron1 = $builder->build({
1150         source => 'Borrower',
1151         value  => {
1152             password => hash_password( PATRON_PW ),
1153         },
1154     });
1155     my $card1 = $patron1->{cardnumber};
1156     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
1157     my $patron_category = $sip_patron1->ptype();
1158     $findpatron = $sip_patron1;
1159     my $item_object = $builder->build_sample_item({
1160         damaged => 0,
1161         withdrawn => 0,
1162         itemlost => 0,
1163         restricted => 0,
1164         homebranch => $branchcode,
1165         holdingbranch => $branchcode,
1166     });
1167     my $itemtype = $item_object->effective_itemtype;
1168
1169     my $mockILS = $mocks->{ils};
1170     my $server = { ils => $mockILS, account => {} };
1171     $mockILS->mock( 'institution', sub { $branchcode; } );
1172     $mockILS->mock( 'supports', sub { return; } );
1173     $mockILS->mock( 'checkout', sub {
1174         shift;
1175         return C4::SIP::ILS->checkout(@_);
1176     });
1177     my $today = dt_from_string;
1178     t::lib::Mocks::mock_userenv({ branchcode => $branchcode, flags => 1 });
1179
1180     my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
1181
1182     my $siprequest = RENEW . 'YN' . siprequestdate($today) .
1183     siprequestdate( $today->clone->add( days => 1) ) .
1184     FID_INST_ID . $branchcode . '|'.
1185     FID_PATRON_ID . $sip_patron1->id . '|' .
1186     FID_ITEM_ID . $item_object->barcode . '|' .
1187     FID_TERMINAL_PWD . 'ignored' . '|';
1188
1189     undef $response;
1190     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
1191     $server->{account}->{inhouse_patron_categories} = "A,$patron_category,Z";
1192     $msg->handle_checkout( $server );
1193     my $respcode = substr( $response, 5, 1 );
1194     is( $respcode, 'N', "Desensitize flag was not set for patron category in inhouse_patron_categories" );
1195
1196     undef $response;
1197     $server->{account}->{inhouse_patron_categories} = "A,B,C";
1198     $msg->handle_checkout( $server );
1199     $respcode = substr( $response, 5, 1 );
1200     is( $respcode, 'Y', "Desensitize flag was set for patron category not in inhouse_patron_categories" );
1201
1202     undef $response;
1203     $server->{account}->{inhouse_patron_categories} = "";
1204     $msg->handle_checkout( $server );
1205     $respcode = substr( $response, 5, 1 );
1206     is( $respcode, 'Y', "Desensitize flag was set for empty inhouse_patron_categories" );
1207
1208     $server->{account}->{inhouse_patron_categories} = "";
1209
1210     undef $response;
1211     $server->{account}->{inhouse_item_types} = "A,B,C";
1212     $msg->handle_checkout( $server );
1213     $respcode = substr( $response, 5, 1 );
1214     is( $respcode, 'Y', "Desensitize flag was set for item type not in inhouse_item_types" );
1215
1216     undef $response;
1217     $server->{account}->{inhouse_item_types} = "";
1218     $msg->handle_checkout( $server );
1219     $respcode = substr( $response, 5, 1 );
1220     is( $respcode, 'Y', "Desensitize flag was set for empty inhouse_item_types" );
1221
1222     undef $response;
1223     $server->{account}->{inhouse_item_types} = "A,$itemtype,Z";
1224     $msg->handle_checkout( $server );
1225     $respcode = substr( $response, 5, 1 );
1226     is( $respcode, 'N', "Desensitize flag was not set for itemtype in inhouse_item_types" );
1227
1228 }
1229
1230 # Helper routines
1231
1232 sub create_mocks {
1233     my ( $response, $findpatron, $branchcode ) = @_; # referenced variables !
1234
1235     # mock write_msg (imported from Sip.pm into Message.pm)
1236     my $mockMsg = Test::MockModule->new( 'C4::SIP::Sip::MsgType' );
1237     $mockMsg->mock( 'write_msg', sub { $$response = $_[1]; } ); # save response
1238
1239     # mock ils object
1240     my $mockILS = Test::MockObject->new;
1241     $mockILS->mock( 'check_inst_id', sub {} );
1242     $mockILS->mock( 'institution_id', sub { $$branchcode; } );
1243     $mockILS->mock( 'find_patron', sub { $$findpatron; } );
1244
1245     return { ils => $mockILS, message => $mockMsg };
1246 }
1247
1248 sub check_field {
1249     my ( $code, $resp, $fld, $expr, $msg, $mode ) = @_;
1250     # mode: contains || equals || regex (by default: equals)
1251
1252     # strip fixed part; prefix to simplify next regex
1253     $resp = '|'. substr( $resp, fixed_length( $code ) );
1254     my $fldval;
1255     if( $resp =~ /\|$fld([^\|]*)\|/ ) {
1256         $fldval = $1;
1257     } elsif( !defined($expr) ) { # field should not be found
1258         ok( 1, $msg );
1259         return;
1260     } else { # test fails
1261         is( 0, 1, "Code $fld not found in '$resp'?" );
1262         return;
1263     }
1264
1265     if( !$mode || $mode eq 'equals' ) { # default
1266         is( $fldval, $expr, $msg );
1267     } elsif( $mode eq 'regex' ) {
1268         is( $fldval =~ /$expr/, 1, $msg );
1269     } else { # contains
1270         is( index( $fldval, $expr ) > -1, 1, $msg );
1271     }
1272 }
1273
1274 sub siprequestdate {
1275     my ( $dt ) = @_;
1276     return $dt->ymd('').(' 'x4).$dt->hms('');
1277 }
1278
1279 sub fixed_length { #length of fixed fields including response code
1280     return {
1281       ( PATRON_STATUS_RESP )  => 37,
1282       ( PATRON_INFO_RESP )    => 61,
1283       ( CHECKIN_RESP )        => 24,
1284       ( CHECKOUT_RESP )       => 24,
1285     }->{$_[0]};
1286 }