3 # Tests for SIP::Sip::MsgType
4 # Please help to extend it!
6 # This file is part of Koha.
8 # Copyright 2016 Rijksmuseum
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.
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.
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>.
24 use Test::More tests => 5;
30 use t::lib::TestBuilder;
33 use Koha::AuthUtils qw(hash_password);
37 use Koha::Old::Checkouts;
41 use C4::SIP::ILS::Patron;
42 use C4::SIP::Sip qw(write_msg);
43 use C4::SIP::Sip::Constants qw(:all);
44 use C4::SIP::Sip::MsgType;
46 use constant PATRON_PW => 'do_not_ever_use_this_one';
49 subtest 'Testing Patron Status Request V2' => sub {
50 my $schema = Koha::Database->new->schema;
51 $schema->storage->txn_begin;
53 $C4::SIP::Sip::protocol_version = 2;
54 test_request_patron_status_v2();
55 $schema->storage->txn_rollback;
58 subtest 'Testing Patron Info Request V2' => sub {
59 my $schema = Koha::Database->new->schema;
60 $schema->storage->txn_begin;
62 $C4::SIP::Sip::protocol_version = 2;
63 test_request_patron_info_v2();
64 $schema->storage->txn_rollback;
67 subtest 'Checkin V2' => sub {
68 my $schema = Koha::Database->new->schema;
69 $schema->storage->txn_begin;
71 $C4::SIP::Sip::protocol_version = 2;
73 $schema->storage->txn_rollback;
76 subtest 'Test hold_patron_bcode' => sub {
77 my $schema = Koha::Database->new->schema;
78 $schema->storage->txn_begin;
80 $C4::SIP::Sip::protocol_version = 2;
81 test_hold_patron_bcode();
82 $schema->storage->txn_rollback;
85 subtest 'Lastseen response' => sub {
87 my $schema = Koha::Database->new->schema;
88 $schema->storage->txn_begin;
91 my $builder = t::lib::TestBuilder->new();
92 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
93 my ( $response, $findpatron );
94 my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
95 my $seen_patron = $builder->build({
98 lastseen => '2001-01-01',
99 password => hash_password( PATRON_PW ),
100 branchcode => $branchcode,
103 my $cardnum = $seen_patron->{cardnumber};
104 my $sip_patron = C4::SIP::ILS::Patron->new( $cardnum );
105 $findpatron = $sip_patron;
107 my $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y '.
108 FID_INST_ID. $branchcode. '|'.
109 FID_PATRON_ID. $cardnum. '|'.
110 FID_PATRON_PWD. PATRON_PW. '|';
111 my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
113 my $server = { ils => $mocks->{ils} };
115 t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '' );
116 $msg->handle_patron_info( $server );
118 isnt( $response, undef, 'At least we got a response.' );
119 my $respcode = substr( $response, 0, 2 );
120 is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
121 $seen_patron = Koha::Patrons->find({ cardnumber => $seen_patron->{cardnumber} });
122 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');
124 t::lib::Mocks::mock_preference( 'TrackLastPatronActivity', '1' );
125 $msg->handle_patron_info( $server );
127 isnt( $response, undef, 'At least we got a response.' );
128 $respcode = substr( $response, 0, 2 );
129 is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
130 $seen_patron = Koha::Patrons->find({ cardnumber => $seen_patron->cardnumber() });
131 is( output_pref({str => $seen_patron->lastseen(), dateonly => 1}), output_pref({dt => dt_from_string(), dateonly => 1}),'Last seen updated if tracking patrons');
132 $schema->storage->txn_rollback;
135 # Here is room for some more subtests
139 sub test_request_patron_status_v2 {
140 my $builder = t::lib::TestBuilder->new();
141 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
142 my ( $response, $findpatron );
143 my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
145 my $patron1 = $builder->build({
146 source => 'Borrower',
148 password => hash_password( PATRON_PW ),
151 my $card1 = $patron1->{cardnumber};
152 my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
153 $findpatron = $sip_patron1;
155 my $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
156 FID_INST_ID. $branchcode. '|'.
157 FID_PATRON_ID. $card1. '|'.
158 FID_PATRON_PWD. PATRON_PW. '|';
159 my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
161 my $server = { ils => $mocks->{ils} };
163 $msg->handle_patron_status( $server );
165 isnt( $response, undef, 'At least we got a response.' );
166 my $respcode = substr( $response, 0, 2 );
167 is( $respcode, PATRON_STATUS_RESP, 'Response code fine' );
169 check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
170 check_field( $respcode, $response, FID_PATRON_ID, $card1, 'Verified patron id' );
171 check_field( $respcode, $response, FID_PERSONAL_NAME, $patron1->{surname}, 'Verified patron name', 'contains' );
172 check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
173 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
174 check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'Verified non-empty screen message', 'regex' );
176 # Now, we pass a wrong password and verify CQ again
177 $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
178 FID_INST_ID. $branchcode. '|'.
179 FID_PATRON_ID. $card1. '|'.
180 FID_PATRON_PWD. 'wrong_password'. '|';
181 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
183 $msg->handle_patron_status( $server );
184 $respcode = substr( $response, 0, 2 );
185 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'Verified code CQ for wrong pw' );
187 # Check empty password and verify CQ again
188 $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
189 FID_INST_ID. $branchcode. '|'.
190 FID_PATRON_ID. $card1. '|'.
192 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
194 $msg->handle_patron_status( $server );
195 $respcode = substr( $response, 0, 2 );
196 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
198 # Finally, we send a wrong card number and check AE, BL
199 # This is done by removing the new patron first
200 Koha::Patrons->search({ cardnumber => $card1 })->delete;
202 $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
203 FID_INST_ID. $branchcode. '|'.
204 FID_PATRON_ID. $card1. '|'.
205 FID_PATRON_PWD. PATRON_PW. '|';
206 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
208 $msg->handle_patron_status( $server );
209 $respcode = substr( $response, 0, 2 );
210 check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
211 check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
212 check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
215 sub test_request_patron_info_v2 {
216 my $builder = t::lib::TestBuilder->new();
217 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
218 my ( $response, $findpatron );
219 my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
221 my $patron2 = $builder->build({
222 source => 'Borrower',
224 password => hash_password( PATRON_PW ),
227 my $card = $patron2->{cardnumber};
228 my $sip_patron2 = C4::SIP::ILS::Patron->new( $card );
229 $findpatron = $sip_patron2;
230 my $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y '.
231 FID_INST_ID. $branchcode. '|'.
232 FID_PATRON_ID. $card. '|'.
233 FID_PATRON_PWD. PATRON_PW. '|';
234 my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
236 my $server = { ils => $mocks->{ils} };
238 $msg->handle_patron_info( $server );
239 isnt( $response, undef, 'At least we got a response.' );
240 my $respcode = substr( $response, 0, 2 );
241 is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
243 check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
244 check_field( $respcode, $response, FID_PATRON_ID, $card, 'Verified patron id' );
245 check_field( $respcode, $response, FID_PERSONAL_NAME, $patron2->{surname}, 'Verified patron name', 'contains' );
246 check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
247 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
248 check_field( $respcode, $response, FID_FEE_LMT, '.*', 'Checked existence of fee limit', 'regex' );
249 check_field( $respcode, $response, FID_HOME_ADDR, $patron2->{address}, 'Address in BD', 'contains' );
250 check_field( $respcode, $response, FID_EMAIL, $patron2->{email}, 'Verified email in BE' );
251 check_field( $respcode, $response, FID_HOME_PHONE, $patron2->{phone}, 'Verified home phone in BF' );
252 # No check for custom fields here (unofficial PB, PC and PI)
253 check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'We have a screen msg', 'regex' );
255 # Test customized patron name in AE with same sip request
256 # This implicitly tests C4::SIP::ILS::Patron->name
257 $server->{account}->{ae_field_template} = "X[% patron.surname %]Y";
258 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
260 $msg->handle_patron_info( $server );
261 $respcode = substr( $response, 0, 2 );
262 check_field( $respcode, $response, FID_PERSONAL_NAME, 'X' . $patron2->{surname} . 'Y', 'Check customized patron name' );
265 $server->{account}->{hide_fields} = "BD,BE,BF,PB";
266 $msg->handle_patron_info( $server );
267 $respcode = substr( $response, 0, 2 );
268 check_field( $respcode, $response, FID_HOME_ADDR, undef, 'Home address successfully stripped from response' );
269 check_field( $respcode, $response, FID_EMAIL, undef, 'Email address successfully stripped from response' );
270 check_field( $respcode, $response, FID_HOME_PHONE, undef, 'Home phone successfully stripped from response' );
271 check_field( $respcode, $response, FID_PATRON_BIRTHDATE, undef, 'Date of birth successfully stripped from response' );
272 $server->{account}->{hide_fields} = "";
274 # Check empty password and verify CQ again
275 $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y '.
276 FID_INST_ID. $branchcode. '|'.
277 FID_PATRON_ID. $card. '|'.
279 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
281 $msg->handle_patron_info( $server );
282 $respcode = substr( $response, 0, 2 );
283 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
284 # Test empty password is OK if account configured to allow
285 $server->{account}->{allow_empty_passwords} = 1;
286 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
288 $msg->handle_patron_info( $server );
289 $respcode = substr( $response, 0, 2 );
290 check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'code CQ should be Y if empty AD allowed' );
292 t::lib::Mocks::mock_preference( 'FailedLoginAttempts', '1' );
293 my $patron = Koha::Patrons->find({ cardnumber => $card });
294 $patron->update({ login_attempts => 0 });
295 is( $patron->account_locked, 0, "Patron account not locked already" );
296 $msg->handle_patron_info( $server );
297 $patron = Koha::Patrons->find({ cardnumber => $card });
298 is( $patron->account_locked, 0, "Patron account is not locked by patron info messages with empty password" );
300 # Finally, we send a wrong card number
301 Koha::Patrons->search({ cardnumber => $card })->delete;
303 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
305 $msg->handle_patron_info( $server );
306 $respcode = substr( $response, 0, 2 );
307 check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
308 check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
309 check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
312 sub test_checkin_v2 {
313 my $builder = t::lib::TestBuilder->new();
314 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
315 my $branchcode2 = $builder->build({ source => 'Branch' })->{branchcode};
316 my ( $response, $findpatron );
317 my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
320 my $patron1 = $builder->build({
321 source => 'Borrower',
323 password => hash_password( PATRON_PW ),
326 my $card1 = $patron1->{cardnumber};
327 my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
328 $findpatron = $sip_patron1;
329 my $item_object = $builder->build_sample_item({
334 homebranch => $branchcode,
335 holdingbranch => $branchcode,
338 my $mockILS = $mocks->{ils};
339 my $server = { ils => $mockILS, account => {} };
340 $mockILS->mock( 'institution', sub { $branchcode; } );
341 $mockILS->mock( 'supports', sub { return; } );
342 $mockILS->mock( 'checkin', sub {
344 return C4::SIP::ILS->checkin(@_);
346 my $today = dt_from_string;
348 # Checkin invalid barcode
349 Koha::Items->search({ barcode => 'not_to_be_found' })->delete;
350 my $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
351 siprequestdate( $today->clone->add( days => 1) ) .
352 FID_INST_ID . $branchcode . '|'.
353 FID_ITEM_ID . 'not_to_be_found' . '|' .
354 FID_TERMINAL_PWD . 'ignored' . '|';
356 my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
357 warnings_like { $msg->handle_checkin( $server ); }
358 [ qr/No item 'not_to_be_found'/, qr/no item found in object to resensitize/ ],
359 'Checkin of invalid item with two warnings';
360 my $respcode = substr( $response, 0, 2 );
361 is( $respcode, CHECKIN_RESP, 'Response code fine' );
362 is( substr($response,2,1), '0', 'OK flag is false' );
363 is( substr($response,5,1), 'Y', 'Alert flag is set' );
364 check_field( $respcode, $response, FID_SCREEN_MSG, 'Invalid Item', 'Check screen msg', 'regex' );
366 # Not checked out, toggle option checked_in_ok
367 $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
368 siprequestdate( $today->clone->add( days => 1) ) .
369 FID_INST_ID . $branchcode . '|'.
370 FID_ITEM_ID . $item_object->barcode . '|' .
371 FID_TERMINAL_PWD . 'ignored' . '|';
373 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
374 $msg->handle_checkin( $server );
375 $respcode = substr( $response, 0, 2 );
376 is( substr($response,2,1), '0', 'OK flag is false when checking in an item that was not checked out' );
377 is( substr($response,5,1), 'Y', 'Alert flag is set' );
378 check_field( $respcode, $response, FID_SCREEN_MSG, 'not checked out', 'Check screen msg', 'regex' );
380 $server->{account}->{checked_in_ok} = 1;
382 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
383 $msg->handle_checkin( $server );
384 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' );
385 is( substr($response,5,1), 'N', 'Alert flag no longer set' );
386 check_field( $respcode, $response, FID_SCREEN_MSG, undef, 'No screen msg' );
388 # Move item to another holding branch to trigger CV of 04 with alert flag
389 t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
390 $item_object->holdingbranch( $branchcode2 )->store();
392 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
393 $msg->handle_checkin( $server );
394 is( substr($response,5,1), 'Y', 'Alert flag is set with check_in_ok, item is checked in but needs transfer' );
395 check_field( $respcode, $response, FID_ALERT_TYPE, '04', 'Got FID_ALERT_TYPE (CV) field with value 04 ( needs transfer )' );
396 $item_object->holdingbranch( $branchcode )->store();
397 t::lib::Mocks::mock_preference( ' AllowReturnToBranch ', 'anywhere' );
399 $server->{account}->{cv_send_00_on_success} = 0;
401 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
402 $msg->handle_checkin( $server );
403 $respcode = substr( $response, 0, 2 );
404 check_field( $respcode, $response, FID_ALERT_TYPE, undef, 'No FID_ALERT_TYPE (CV) field' );
405 $server->{account}->{cv_send_00_on_success} = 1;
407 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
408 $msg->handle_checkin( $server );
409 $respcode = substr( $response, 0, 2 );
410 check_field( $respcode, $response, FID_ALERT_TYPE, '00', 'FID_ALERT_TYPE (CV) field is 00' );
411 $server->{account}->{checked_in_ok} = 0;
412 $server->{account}->{cv_send_00_on_success} = 0;
414 t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '1' );
415 $server->{account}->{checked_in_ok} = 0;
416 $server->{account}->{cv_triggers_alert} = 0;
418 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
419 $msg->handle_checkin( $server );
420 $respcode = substr( $response, 0, 2 );
421 is( substr( $response, 5, 1 ), 'Y', 'Checkin without CV triggers alert flag when cv_triggers_alert is off' );
422 t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '0' );
423 $server->{account}->{cv_triggers_alert} = 1;
425 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
426 $msg->handle_checkin( $server );
427 $respcode = substr( $response, 0, 2 );
428 is( substr( $response, 5, 1 ), 'N', 'Checkin without CV does not trigger alert flag when cv_triggers_alert is on' );
429 $server->{account}->{cv_triggers_alert} = 0;
430 t::lib::Mocks::mock_preference( 'RecordLocalUseOnReturn', '1' );
432 $server->{account}->{checked_in_ok} = 1;
433 $server->{account}->{ct_always_send} = 0;
435 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
436 $msg->handle_checkin( $server );
437 $respcode = substr( $response, 0, 2 );
438 check_field( $respcode, $response, FID_DESTINATION_LOCATION, undef, 'No FID_DESTINATION_LOCATION (CT) field' );
439 $server->{account}->{ct_always_send} = 1;
441 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
442 $msg->handle_checkin( $server );
443 $respcode = substr( $response, 0, 2 );
444 check_field( $respcode, $response, FID_DESTINATION_LOCATION, q{}, 'FID_DESTINATION_LOCATION (CT) field is empty but present' );
445 $server->{account}->{checked_in_ok} = 0;
446 $server->{account}->{ct_always_send} = 0;
448 # Checkin at wrong branch: issue item and switch branch, and checkin
449 my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item_object->itemnumber })->store;
450 $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
451 t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
453 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
454 $msg->handle_checkin( $server );
455 is( substr($response,2,1), '0', 'OK flag is false when we check in at the wrong branch and we do not allow it' );
456 is( substr($response,5,1), 'Y', 'Alert flag is set' );
457 check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed', 'Check screen msg' );
458 $branchcode = $item_object->homebranch; # switch back
459 t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
461 # Data corrupted: add same issue_id to old_issues
462 Koha::Old::Checkout->new({ issue_id => $issue->issue_id })->store;
464 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
465 warnings_like { $msg->handle_checkin( $server ); }
466 [ qr/Duplicate entry/, qr/data corrupted/ ],
467 'DBIx error on duplicate issue_id';
468 is( substr($response,2,1), '0', 'OK flag is false when we encounter data corruption in old_issues' );
469 is( substr($response,5,1), 'Y', 'Alert flag is set' );
470 check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed: data problem', 'Check screen msg' );
472 # Finally checkin without problems (remove duplicate id)
473 Koha::Old::Checkouts->search({ issue_id => $issue->issue_id })->delete;
475 $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
476 $msg->handle_checkin( $server );
477 is( substr($response,2,1), '1', 'OK flag is true when we checkin after removing the duplicate' );
478 is( substr($response,5,1), 'N', 'Alert flag is not set' );
479 is( Koha::Checkouts->find( $issue->issue_id ), undef,
480 'Issue record is gone now' );
483 sub test_hold_patron_bcode {
484 my $builder = t::lib::TestBuilder->new();
485 my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
486 my ( $response, $findpatron );
487 my $mocks = create_mocks( \$response, \$findpatron, \$branchcode );
489 my $item = $builder->build({
491 value => { damaged => 0, withdrawn => 0, itemlost => 0, restricted => 0, homebranch => $branchcode, holdingbranch => $branchcode },
493 my $item_object = Koha::Items->find( $item->{itemnumber} );
495 my $server = { ils => $mocks->{ils} };
496 my $sip_item = C4::SIP::ILS::Item->new( $item->{barcode} );
498 is( $sip_item->hold_patron_bcode, q{}, "SIP item with no hold returns empty string" );
500 my $resp .= C4::SIP::Sip::maybe_add( FID_CALL_NUMBER, $sip_item->hold_patron_bcode, $server );
501 is( $resp, q{}, "maybe_add returns empty string for SIP item with no hold returns empty string" );
507 my ( $response, $findpatron, $branchcode ) = @_; # referenced variables !
509 # mock write_msg (imported from Sip.pm into Message.pm)
510 my $mockMsg = Test::MockModule->new( 'C4::SIP::Sip::MsgType' );
511 $mockMsg->mock( 'write_msg', sub { $$response = $_[1]; } ); # save response
514 my $mockILS = Test::MockObject->new;
515 $mockILS->mock( 'check_inst_id', sub {} );
516 $mockILS->mock( 'institution_id', sub { $$branchcode; } );
517 $mockILS->mock( 'find_patron', sub { $$findpatron; } );
519 return { ils => $mockILS, message => $mockMsg };
523 my ( $code, $resp, $fld, $expr, $msg, $mode ) = @_;
524 # mode: contains || equals || regex (by default: equals)
526 # strip fixed part; prefix to simplify next regex
527 $resp = '|'. substr( $resp, fixed_length( $code ) );
529 if( $resp =~ /\|$fld([^\|]*)\|/ ) {
531 } elsif( !defined($expr) ) { # field should not be found
534 } else { # test fails
535 is( 0, 1, "Code $fld not found in '$resp'?" );
539 if( !$mode || $mode eq 'equals' ) { # default
540 is( $fldval, $expr, $msg );
541 } elsif( $mode eq 'regex' ) {
542 is( $fldval =~ /$expr/, 1, $msg );
544 is( index( $fldval, $expr ) > -1, 1, $msg );
550 return $dt->ymd('').(' 'x4).$dt->hms('');
553 sub fixed_length { #length of fixed fields including response code
555 ( PATRON_STATUS_RESP ) => 37,
556 ( PATRON_INFO_RESP ) => 61,
557 ( CHECKIN_RESP ) => 24,