]> git.koha-community.org Git - koha.git/blob - t/db_dependent/SIP/Message.t
Bug 18996: Add checkin subtest in SIP/Message.t
[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 => 3;
25 use Test::MockObject;
26 use Test::MockModule;
27 use Test::Warn;
28
29 use t::lib::Mocks;
30 use t::lib::TestBuilder;
31
32 use Koha::Database;
33 use Koha::AuthUtils qw(hash_password);
34 use Koha::DateUtils;
35 use Koha::Items;
36 use Koha::Checkouts;
37 use Koha::Old::Checkouts;
38
39 use C4::SIP::ILS;
40 use C4::SIP::ILS::Patron;
41 use C4::SIP::Sip qw(write_msg);
42 use C4::SIP::Sip::Constants qw(:all);
43 use C4::SIP::Sip::MsgType;
44
45 use constant PATRON_PW => 'do_not_ever_use_this_one';
46
47 our $fixed_length = { #length of fixed fields including response code
48     ( PATRON_STATUS_RESP ) => 37,
49     ( PATRON_INFO_RESP )   => 61,
50     ( CHECKIN_RESP )       => 24,
51 };
52
53 our $schema = Koha::Database->new->schema;
54 our $builder = t::lib::TestBuilder->new();
55
56 # COMMON: Some common stuff for all/most subtests
57 our ( $response, $findpatron, $branchcode );
58 $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
59 # mock write_msg (imported from Sip.pm into Message.pm)
60 my $mockMsg = Test::MockModule->new( 'C4::SIP::Sip::MsgType' );
61 $mockMsg->mock( 'write_msg', sub { $response = $_[1]; } ); # save response
62 # mock ils object
63 our $mockILS = Test::MockObject->new;
64 $mockILS->mock( 'check_inst_id', sub {} );
65 $mockILS->mock( 'institution_id', sub { $branchcode; } );
66 $mockILS->mock( 'find_patron', sub { $findpatron; } );
67
68 # START testing
69 subtest 'Testing Patron Status Request V2' => sub {
70     $schema->storage->txn_begin;
71     plan tests => 13;
72     $C4::SIP::Sip::protocol_version = 2;
73     test_request_patron_status_v2();
74     $schema->storage->txn_rollback;
75 };
76
77 subtest 'Testing Patron Info Request V2' => sub {
78     $schema->storage->txn_begin;
79     plan tests => 18;
80     $C4::SIP::Sip::protocol_version = 2;
81     test_request_patron_info_v2();
82     $schema->storage->txn_rollback;
83 };
84
85 subtest 'Checkin V2' => sub {
86     $schema->storage->txn_begin;
87     plan tests => 21;
88     $C4::SIP::Sip::protocol_version = 2;
89     test_checkin_v2();
90     $schema->storage->txn_rollback;
91 };
92
93 # Here is room for some more subtests
94
95 # END of main code
96
97 sub test_request_patron_status_v2 {
98     my $patron1 = $builder->build({
99         source => 'Borrower',
100         value  => {
101             password => hash_password( PATRON_PW ),
102         },
103     });
104     my $card1 = $patron1->{cardnumber};
105     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
106     $findpatron = $sip_patron1;
107
108     my $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
109         FID_INST_ID. $branchcode. '|'.
110         FID_PATRON_ID. $card1. '|'.
111         FID_PATRON_PWD. PATRON_PW. '|';
112     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
113
114     my $server = { ils => $mockILS };
115     undef $response;
116     $msg->handle_patron_status( $server );
117
118     isnt( $response, undef, 'At least we got a response.' );
119     my $respcode = substr( $response, 0, 2 );
120     is( $respcode, PATRON_STATUS_RESP, 'Response code fine' );
121
122     check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
123     check_field( $respcode, $response, FID_PATRON_ID, $card1, 'Verified patron id' );
124     check_field( $respcode, $response, FID_PERSONAL_NAME, $patron1->{surname}, 'Verified patron name', 'contains' );
125     check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
126     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
127     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'Verified non-empty screen message', 'regex' );
128
129     # Now, we pass a wrong password and verify CQ again
130     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
131         FID_INST_ID. $branchcode. '|'.
132         FID_PATRON_ID. $card1. '|'.
133         FID_PATRON_PWD. 'wrong_password'. '|';
134     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
135     undef $response;
136     $msg->handle_patron_status( $server );
137     $respcode = substr( $response, 0, 2 );
138     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'Verified code CQ for wrong pw' );
139
140     # Check empty password and verify CQ again
141     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
142         FID_INST_ID. $branchcode. '|'.
143         FID_PATRON_ID. $card1. '|'.
144         FID_PATRON_PWD. '|';
145     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
146     undef $response;
147     $msg->handle_patron_status( $server );
148     $respcode = substr( $response, 0, 2 );
149     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
150
151     # Finally, we send a wrong card number and check AE, BL
152     # This is done by removing the new patron first
153     $schema->resultset('Borrower')->search({ cardnumber => $card1 })->delete;
154     undef $findpatron;
155     $siprequest = PATRON_STATUS_REQ. 'engYYYYMMDDZZZZHHMMSS'.
156         FID_INST_ID. $branchcode. '|'.
157         FID_PATRON_ID. $card1. '|'.
158         FID_PATRON_PWD. PATRON_PW. '|';
159     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
160     undef $response;
161     $msg->handle_patron_status( $server );
162     $respcode = substr( $response, 0, 2 );
163     check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
164     check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
165     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
166 }
167
168 sub test_request_patron_info_v2 {
169     my $patron2 = $builder->build({
170         source => 'Borrower',
171         value  => {
172             password => hash_password( PATRON_PW ),
173         },
174     });
175     my $card = $patron2->{cardnumber};
176     my $sip_patron2 = C4::SIP::ILS::Patron->new( $card );
177     $findpatron = $sip_patron2;
178     my $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y         '.
179         FID_INST_ID. $branchcode. '|'.
180         FID_PATRON_ID. $card. '|'.
181         FID_PATRON_PWD. PATRON_PW. '|';
182     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
183
184     my $server = { ils => $mockILS };
185     undef $response;
186     $msg->handle_patron_info( $server );
187     isnt( $response, undef, 'At least we got a response.' );
188     my $respcode = substr( $response, 0, 2 );
189     is( $respcode, PATRON_INFO_RESP, 'Response code fine' );
190
191     check_field( $respcode, $response, FID_INST_ID, $branchcode , 'Verified institution id' );
192     check_field( $respcode, $response, FID_PATRON_ID, $card, 'Verified patron id' );
193     check_field( $respcode, $response, FID_PERSONAL_NAME, $patron2->{surname}, 'Verified patron name', 'contains' );
194     check_field( $respcode, $response, FID_VALID_PATRON, 'Y', 'Verified code BL' );
195     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'Verified code CQ' );
196     check_field( $respcode, $response, FID_FEE_LMT, '.*', 'Checked existence of fee limit', 'regex' );
197     check_field( $respcode, $response, FID_HOME_ADDR, $patron2->{address}, 'Address in BD', 'contains' );
198     check_field( $respcode, $response, FID_EMAIL, $patron2->{email}, 'Verified email in BE' );
199     check_field( $respcode, $response, FID_HOME_PHONE, $patron2->{phone}, 'Verified home phone in BF' );
200     # No check for custom fields here (unofficial PB, PC and PI)
201     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'We have a screen msg', 'regex' );
202
203     # Test customized patron name in AE with same sip request
204     # This implicitly tests C4::SIP::ILS::Patron->name
205     $server->{account}->{ae_field_template} = "X[% patron.surname %]Y";
206     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
207     undef $response;
208     $msg->handle_patron_info( $server );
209     $respcode = substr( $response, 0, 2 );
210     check_field( $respcode, $response, FID_PERSONAL_NAME, 'X' . $patron2->{surname} . 'Y', 'Check customized patron name' );
211
212     # Check empty password and verify CQ again
213     $siprequest = PATRON_INFO. 'engYYYYMMDDZZZZHHMMSS'.'Y         '.
214         FID_INST_ID. $branchcode. '|'.
215         FID_PATRON_ID. $card. '|'.
216         FID_PATRON_PWD. '|';
217     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
218     undef $response;
219     $msg->handle_patron_info( $server );
220     $respcode = substr( $response, 0, 2 );
221     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'N', 'code CQ should be N for empty AD' );
222     # Test empty password is OK if account configured to allow
223     $server->{account}->{allow_empty_passwords} = 1;
224     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
225     undef $response;
226     $msg->handle_patron_info( $server );
227     $respcode = substr( $response, 0, 2 );
228     check_field( $respcode, $response, FID_VALID_PATRON_PWD, 'Y', 'code CQ should be Y if empty AD allowed' );
229
230     # Finally, we send a wrong card number
231     $schema->resultset('Borrower')->search({ cardnumber => $card })->delete;
232     undef $findpatron;
233     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
234     undef $response;
235     $msg->handle_patron_info( $server );
236     $respcode = substr( $response, 0, 2 );
237     check_field( $respcode, $response, FID_VALID_PATRON, 'N', 'Verified code BL for wrong cardnumber' );
238     check_field( $respcode, $response, FID_PERSONAL_NAME, '', 'Name should be empty now' );
239     check_field( $respcode, $response, FID_SCREEN_MSG, '.+', 'But we have a screen msg', 'regex' );
240 }
241
242 sub test_checkin_v2 {
243     # create some data
244     my $patron1 = $builder->build({
245         source => 'Borrower',
246         value  => {
247             password => hash_password( PATRON_PW ),
248         },
249     });
250     my $card1 = $patron1->{cardnumber};
251     my $sip_patron1 = C4::SIP::ILS::Patron->new( $card1 );
252     $findpatron = $sip_patron1;
253     my $item = $builder->build({
254         source => 'Item',
255         value => { damaged => 0, withdrawn => 0, itemlost => 0, restricted => 0, homebranch => $branchcode, holdingbranch => $branchcode },
256     });
257
258     my $server = { ils => $mockILS, account => {} };
259     $mockILS->mock( 'institution', sub { $branchcode; } );
260     $mockILS->mock( 'supports', sub { return; } );
261     $mockILS->mock( 'checkin', sub {
262         shift;
263         return C4::SIP::ILS->checkin(@_);
264     });
265     my $today = dt_from_string;
266
267     # Checkin invalid barcode
268     Koha::Items->search({ barcode => 'not_to_be_found' })->delete;
269     my $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
270         siprequestdate( $today->clone->add( days => 1) ) .
271         FID_INST_ID . $branchcode . '|'.
272         FID_ITEM_ID . 'not_to_be_found' . '|' .
273         FID_TERMINAL_PWD . 'ignored' . '|';
274     undef $response;
275     my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
276     warnings_like { $msg->handle_checkin( $server ); }
277         [ qr/No item 'not_to_be_found'/, qr/no item found in object to resensitize/ ],
278         'Checkin of invalid item with two warnings';
279     my $respcode = substr( $response, 0, 2 );
280     is( $respcode, CHECKIN_RESP, 'Response code fine' );
281     is( substr($response,2,1), '0', 'OK flag is false' );
282     is( substr($response,5,1), 'Y', 'Alert flag is set' );
283     check_field( $respcode, $response, FID_SCREEN_MSG, 'Invalid Item', 'Check screen msg', 'regex' );
284
285     # Not checked out, toggle option checked_in_ok
286     $siprequest = CHECKIN . 'N' . 'YYYYMMDDZZZZHHMMSS' .
287         siprequestdate( $today->clone->add( days => 1) ) .
288         FID_INST_ID . $branchcode . '|'.
289         FID_ITEM_ID . $item->{barcode} . '|' .
290         FID_TERMINAL_PWD . 'ignored' . '|';
291     undef $response;
292     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
293     $msg->handle_checkin( $server );
294     $respcode = substr( $response, 0, 2 );
295     is( substr($response,2,1), '0', 'OK flag is false when checking in an item that was not checked out' );
296     is( substr($response,5,1), 'Y', 'Alert flag is set' );
297     check_field( $respcode, $response, FID_SCREEN_MSG, 'not checked out', 'Check screen msg', 'regex' );
298     # Toggle option
299     $server->{account}->{checked_in_ok} = 1;
300     undef $response;
301     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
302     $msg->handle_checkin( $server );
303     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' );
304     is( substr($response,5,1), 'Y', 'Alert flag is set' );
305     check_field( $respcode, $response, FID_SCREEN_MSG, undef, 'No screen msg' );
306     $server->{account}->{checked_in_ok} = 0;
307
308     # Checkin at wrong branch: issue item and switch branch, and checkin
309     my $issue = Koha::Checkout->new({ branchcode => $branchcode, borrowernumber => $patron1->{borrowernumber}, itemnumber => $item->{itemnumber} })->store;
310     $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
311     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
312     undef $response;
313     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
314     $msg->handle_checkin( $server );
315     is( substr($response,2,1), '0', 'OK flag is false when we check in at the wrong branch and we do not allow it' );
316     is( substr($response,5,1), 'Y', 'Alert flag is set' );
317     check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed', 'Check screen msg' );
318     $branchcode = $item->{homebranch};  # switch back
319     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
320
321     # Data corrupted: add same issue_id to old_issues
322     Koha::Old::Checkout->new({ issue_id => $issue->issue_id })->store;
323     undef $response;
324     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
325     warnings_like { $msg->handle_checkin( $server ); }
326         [ qr/Duplicate entry/, qr/data corrupted/ ],
327         'DBIx error on duplicate issue_id';
328     is( substr($response,2,1), '0', 'OK flag is false when we encounter data corruption in old_issues' );
329     is( substr($response,5,1), 'Y', 'Alert flag is set' );
330     check_field( $respcode, $response, FID_SCREEN_MSG, 'Checkin failed: data problem', 'Check screen msg' );
331
332     # Finally checkin without problems (remove duplicate id)
333     Koha::Old::Checkouts->search({ issue_id => $issue->issue_id })->delete;
334     undef $response;
335     $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
336     $msg->handle_checkin( $server );
337     is( substr($response,2,1), '1', 'OK flag is true when we checkin after removing the duplicate' );
338     is( substr($response,5,1), 'N', 'Alert flag is not set' );
339     is( Koha::Checkouts->find( $issue->issue_id ), undef,
340         'Issue record is gone now' );
341 }
342
343 # Helper routines
344
345 sub check_field {
346     my ( $code, $resp, $fld, $expr, $msg, $mode ) = @_;
347     # mode: contains || equals || regex (by default: equals)
348
349     # strip fixed part; prefix to simplify next regex
350     $resp = '|'. substr( $resp, $fixed_length->{$code} );
351     my $fldval;
352     if( $resp =~ /\|$fld([^\|]*)\|/ ) {
353         $fldval = $1;
354     } elsif( !defined($expr) ) { # field should not be found
355         ok( 1, $msg );
356         return;
357     } else { # test fails
358         is( 0, 1, "Code $fld not found in '$resp'?" );
359         return;
360     }
361
362     if( !$mode || $mode eq 'equals' ) { # default
363         is( $fldval, $expr, $msg );
364     } elsif( $mode eq 'regex' ) {
365         is( $fldval =~ /$expr/, 1, $msg );
366     } else { # contains
367         is( index( $fldval, $expr ) > -1, 1, $msg );
368     }
369 }
370
371 sub siprequestdate {
372     my ( $dt ) = @_;
373     return $dt->ymd('').(' 'x4).$dt->hms('');
374 }