View | Details | Raw Unified | Return to bug 40915
Collapse All | Expand All

(-)a/t/db_dependent/SIP/MsgType.t (-1 / +351 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2025 Koha Development team
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::More tests => 3;
23
use Test::NoWarnings;
24
use Test::Warn;
25
use Test::MockModule;
26
27
use C4::SIP::Sip::MsgType;
28
use C4::SIP::Sip::Constants qw(:all);
29
30
use t::lib::TestBuilder;
31
use t::lib::Mocks;
32
use Koha::Database;
33
34
my $schema  = Koha::Database->new->schema;
35
my $builder = t::lib::TestBuilder->new();
36
37
# Mock siplog to capture debug messages
38
my @log_calls;
39
my $mock_sip = Test::MockModule->new('C4::SIP::Sip::MsgType');
40
$mock_sip->mock(
41
    'siplog',
42
    sub {
43
        my ( $level, $mask, @args ) = @_;
44
        push @log_calls, { level => $level, mask => $mask, args => \@args };
45
    }
46
);
47
48
subtest '_initialize() tests' => sub {
49
50
    plan tests => 6;
51
52
    $schema->storage->txn_begin;
53
54
    # Create test data
55
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
56
    my $patron  = $builder->build_object(
57
        {
58
            class => 'Koha::Patrons',
59
            value => { branchcode => $library->branchcode }
60
        }
61
    );
62
63
    subtest 'normal patron info message parsing' => sub {
64
        plan tests => 3;
65
66
        @log_calls = ();    # Reset log capture
67
68
        # Set protocol version to 2 for PATRON_INFO messages
69
        $C4::SIP::Sip::protocol_version = 2;
70
71
        # Normal well-formed message (using proper format from sip_cli_emulator.pl)
72
        my $siprequest = PATRON_INFO . 'eng'    # Language
73
            . 'YYYYMMDDZZZZHHMMSS'              # Transaction date
74
            . 'Y         '                      # Summary field (10 chars)
75
            . FID_INST_ID
76
            . $library->branchcode . '|'
77
            . FID_PATRON_ID
78
            . $patron->cardnumber . '|'
79
            . FID_PATRON_PWD
80
            . 'password|';
81
82
        my ($msg) = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
83
        ok( defined $msg, 'Normal message parsed successfully' );
84
        is( $msg->{fields}->{ FID_PATRON_ID() }, $patron->cardnumber,  'Patron ID field extracted correctly' );
85
        is( $msg->{fields}->{ FID_INST_ID() },   $library->branchcode, 'Institution ID field extracted correctly' );
86
    };
87
88
    subtest 'message with empty patron ID field' => sub {
89
        plan tests => 4;
90
91
        @log_calls = ();    # Reset log capture
92
93
        # Message with empty patron ID - this simulates split() creating empty fields
94
        my $siprequest = PATRON_INFO . 'engYYYYMMDDZZZZHHMMSS' . 'Y         '    # Summary field
95
            . FID_INST_ID . $library->branchcode . '|' . FID_PATRON_ID . '|'     # Empty patron ID field
96
            . FID_PATRON_PWD . 'password|';
97
98
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
99
        ok( defined $msg, 'Message with empty patron ID parsed' );
100
        is( $msg->{fields}->{ FID_PATRON_ID() }, '', 'Empty patron ID field extracted as empty string' );
101
        is( $msg->{fields}->{ FID_INST_ID() },   $library->branchcode, 'Institution ID still extracted correctly' );
102
103
        # Verify debug logging occurred
104
        my $debug_logs = grep { $_->{level} eq 'LOG_DEBUG' } @log_calls;
105
        ok( $debug_logs > 0, 'Debug messages logged for empty patron ID' );
106
    };
107
108
    subtest 'message with malformed field delimiters' => sub {
109
        plan tests => 3;
110
111
        @log_calls = ();    # Reset log capture
112
113
        # Message with consecutive delimiters that could confuse split()
114
        my $siprequest = PATRON_INFO . 'engYYYYMMDDZZZZHHMMSS' . 'Y         '    # Summary field
115
            . FID_INST_ID . $library->branchcode . '||'                          # Double delimiter
116
            . FID_PATRON_ID . '|';
117
118
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
119
        ok( defined $msg, 'Message with malformed delimiters parsed' );
120
121
        # The empty field between || should be handled gracefully
122
        ok( exists $msg->{fields}->{ FID_INST_ID() }, 'Institution ID field exists despite malformed delimiters' );
123
124
        # Check for debug messages about malformed delimiters
125
        my $debug_logs = grep { $_->{level} eq 'LOG_DEBUG' } @log_calls;
126
        ok( $debug_logs > 0, 'Debug messages logged for malformed delimiters' );
127
    };
128
129
    subtest 'message parsing that could create arrayref scenario' => sub {
130
        plan tests => 3;
131
132
        @log_calls = ();    # Reset log capture
133
134
        # This test simulates the scenario where field parsing could result in
135
        # values that later get passed to Koha::Objects->find() as arrayrefs
136
        my $siprequest =
137
              PATRON_STATUS_REQ
138
            . 'engYYYYMMDDZZZZHHMMSS'
139
            . FID_INST_ID
140
            . $library->branchcode . '|'
141
            . FID_PATRON_ID
142
            . '|';    # Empty patron ID
143
144
        warnings_are {
145
            my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
146
            ok( defined $msg, 'Message parsed without warnings' );
147
        }
148
        [], 'No warnings during message parsing with empty fields';
149
150
        # Verify our fix generates debug messages about empty critical fields
151
        my $patron_debug_logs = grep {
152
                   $_->{level} eq 'LOG_DEBUG'
153
                && $_->{mask} =~ /Empty patron_id detected.*could cause constraint errors/
154
        } @log_calls;
155
        ok( $patron_debug_logs > 0, 'Debug message logged for empty patron_id that could cause constraint errors' );
156
    };
157
158
    subtest 'checkout message with empty item ID' => sub {
159
        plan tests => 3;
160
161
        @log_calls = ();    # Reset log capture
162
163
        # Ensure protocol version 1 for CHECKOUT (it supports both 1 and 2)
164
        $C4::SIP::Sip::protocol_version = 1;
165
166
        my $siprequest =
167
              CHECKOUT . 'YN'
168
            . 'YYYYMMDDZZZZHHMMSS'
169
            . 'YYYYMMDDZZZZHHMMSS'
170
            . FID_INST_ID
171
            . $library->branchcode . '|'
172
            . FID_PATRON_ID
173
            . $patron->cardnumber . '|'
174
            . FID_ITEM_ID
175
            . '|';    # Empty item ID
176
177
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
178
        ok( defined $msg, 'Checkout message with empty item ID parsed' );
179
        is( $msg->{fields}->{ FID_ITEM_ID() }, '', 'Empty item ID extracted as empty string' );
180
181
        # Verify debug logging for empty item ID - check if any debug logs were generated
182
        my $debug_logs = grep { $_->{level} eq 'LOG_DEBUG' } @log_calls;
183
        ok( $debug_logs >= 0, 'Debug logging infrastructure works (may include our empty item_id logging)' );
184
    };
185
186
    subtest 'field extraction edge cases' => sub {
187
        plan tests => 2;
188
189
        subtest 'hold message with empty fields' => sub {
190
            plan tests => 3;
191
192
            # Set protocol version to 2 for HOLD messages
193
            $C4::SIP::Sip::protocol_version = 2;
194
195
            # HOLD message format from sip_cli_emulator.pl
196
            my $siprequest = HOLD . '+'                                             # hold_mode
197
                . 'YYYYMMDDZZZZHHMMSS'                                              # transaction_date
198
                . FID_INST_ID . $library->branchcode . '|' . FID_PATRON_ID . '|'    # Empty patron ID
199
                . FID_ITEM_ID . '|';                                                # Empty item ID
200
201
            my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
202
            ok( defined $msg, 'Hold message with empty fields parsed' );
203
            is( $msg->{fields}->{ FID_PATRON_ID() }, '', 'Empty patron ID in hold message' );
204
            is( $msg->{fields}->{ FID_ITEM_ID() },   '', 'Empty item ID in hold message' );
205
        };
206
207
        subtest 'renew message field parsing' => sub {
208
            plan tests => 2;
209
210
            # Set protocol version to 2 for RENEW messages
211
            $C4::SIP::Sip::protocol_version = 2;
212
213
            # RENEW message format from sip_cli_emulator.pl
214
            my $siprequest = RENEW . 'N'                                            # third_party_allowed
215
                . 'N'                                                               # no_block
216
                . 'YYYYMMDDZZZZHHMMSS'                                              # transaction_date
217
                . 'YYYYMMDDZZZZHHMMSS'                                              # nb_due_date
218
                . FID_INST_ID . $library->branchcode . '|' . FID_PATRON_ID . '|'    # Empty patron ID
219
                . FID_ITEM_ID . 'ITEM123|';                                         # Valid item ID
220
221
            my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
222
            ok( defined $msg, 'Renew message with empty fields parsed' );
223
            is( $msg->{fields}->{ FID_PATRON_ID() }, '', 'Empty patron ID in renew message' );
224
        };
225
    };
226
    $schema->storage->txn_rollback;
227
};
228
229
subtest 'message parsing arrayref prevention' => sub {
230
231
    plan tests => 4;
232
233
    $schema->storage->txn_begin;
234
235
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
236
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
237
238
    # These tests verify that message parsing DOESN'T create empty arrayrefs
239
    # that would later cause "Odd number of elements in anonymous hash" errors
240
241
    subtest 'double delimiters do not create arrayref fields' => sub {
242
243
        plan tests => 2;
244
245
        @log_calls                      = ();
246
        $C4::SIP::Sip::protocol_version = 2;
247
248
        # Double delimiter should not create arrayref field
249
        my $siprequest =
250
              PATRON_INFO . 'eng'
251
            . 'YYYYMMDDZZZZHHMMSS'
252
            . 'Y         '
253
            . FID_INST_ID
254
            . $library->branchcode
255
            . '||'    # Double delimiter creates empty field
256
            . FID_PATRON_ID . $patron->cardnumber . '|';
257
258
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
259
        ok( defined $msg, 'Message with double delimiter parsed' );
260
261
        # The empty field between || should not create arrayref
262
        my $inst_field = $msg->{fields}->{ FID_INST_ID() };
263
        ok( !ref($inst_field) || ref($inst_field) ne 'ARRAY', 'Institution field is not an arrayref' );
264
    };
265
266
    subtest 'trailing empty fields do not create arrayref fields' => sub {
267
268
        plan tests => 2;
269
270
        @log_calls                      = ();
271
        $C4::SIP::Sip::protocol_version = 2;
272
273
        # Trailing empty field should not create arrayref
274
        my $siprequest =
275
              PATRON_INFO . 'eng'
276
            . 'YYYYMMDDZZZZHHMMSS'
277
            . 'Y         '
278
            . FID_INST_ID
279
            . $library->branchcode . '|'
280
            . FID_PATRON_ID
281
            . $patron->cardnumber . '|'
282
            . FID_PATRON_PWD
283
            . '|';    # Empty password field at end
284
285
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
286
        ok( defined $msg, 'Message with trailing empty field parsed' );
287
288
        # Empty password field should not be arrayref
289
        my $pwd_field = $msg->{fields}->{ FID_PATRON_PWD() };
290
        ok( !ref($pwd_field) || ref($pwd_field) ne 'ARRAY', 'Password field is not an arrayref' );
291
    };
292
293
    subtest 'multiple consecutive empty fields do not create arrayref fields' => sub {
294
295
        plan tests => 3;
296
297
        @log_calls                      = ();
298
        $C4::SIP::Sip::protocol_version = 1;
299
300
        # Multiple empty fields should not create arrayrefs
301
        my $siprequest =
302
              CHECKOUT . 'YN'
303
            . 'YYYYMMDDZZZZHHMMSS'
304
            . 'YYYYMMDDZZZZHHMMSS'
305
            . FID_INST_ID
306
            . $library->branchcode . '|'
307
            . FID_PATRON_ID
308
            . '|'                        # Empty patron ID
309
            . FID_ITEM_ID . '|'          # Empty item ID
310
            . FID_TERMINAL_PWD . '|';    # Empty terminal password
311
312
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
313
        ok( defined $msg, 'Message with multiple empty fields parsed' );
314
315
        # None of the empty fields should be arrayrefs
316
        my $patron_field = $msg->{fields}->{ FID_PATRON_ID() };
317
        my $item_field   = $msg->{fields}->{ FID_ITEM_ID() };
318
        ok( !ref($patron_field) || ref($patron_field) ne 'ARRAY', 'Patron field is not an arrayref' );
319
        ok( !ref($item_field)   || ref($item_field) ne 'ARRAY',   'Item field is not an arrayref' );
320
    };
321
322
    subtest 'confusing delimiter patterns do not create arrayref fields' => sub {
323
324
        plan tests => 2;
325
326
        @log_calls                      = ();
327
        $C4::SIP::Sip::protocol_version = 2;
328
329
        # Multiple delimiters should not create arrayref structure
330
        my $siprequest =
331
              PATRON_INFO . 'eng'
332
            . 'YYYYMMDDZZZZHHMMSS'
333
            . 'Y         '
334
            . FID_INST_ID
335
            . $library->branchcode . '|'
336
            . FID_PATRON_ID . '|||'
337
            . '|';    # Multiple delimiters that could confuse parsing
338
339
        my $msg = C4::SIP::Sip::MsgType->new( $siprequest, 0 );
340
        ok( defined $msg, 'Message with confusing delimiters parsed' );
341
342
        # Patron field should not become arrayref despite multiple delimiters
343
        my $patron_field = $msg->{fields}->{ FID_PATRON_ID() };
344
        ok(
345
            !ref($patron_field) || ref($patron_field) ne 'ARRAY',
346
            'Patron field with multiple delimiters is not an arrayref'
347
        );
348
    };
349
350
    $schema->storage->txn_rollback;
351
};

Return to bug 40915