Line 0
Link Here
|
0 |
- |
1 |
#!/usr/bin/perl |
|
|
2 |
|
3 |
# Copyright 2023 Rijksmuseum |
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 <http://www.gnu.org/licenses>. |
19 |
|
20 |
use Modern::Perl; |
21 |
use Data::Dumper qw( Dumper ); |
22 |
use Test::More tests => 5; |
23 |
use Test::Exception; |
24 |
use Test::Warn; |
25 |
use Try::Tiny; |
26 |
|
27 |
use Koha::Validator; |
28 |
use Koha::Email; |
29 |
|
30 |
our $validator; |
31 |
|
32 |
subtest 'new' => sub { |
33 |
plan tests => 2; |
34 |
|
35 |
$validator = Koha::Validator->new; |
36 |
is( ref($validator), 'Koha::Validator', 'Object type' ); |
37 |
is( $validator->option('throw_exception'), 1, 'Check if option has default value' ); |
38 |
}; |
39 |
|
40 |
subtest 'option' => sub { |
41 |
plan tests => 10; |
42 |
|
43 |
is( ref( $validator->option ), 'Koha::Validator', 'Called option without parameters' ); |
44 |
is( $validator->option('a'), undef, 'Check unknown option' ); |
45 |
is( $validator->option('break_on_error'), 1, 'Check option break_on_error' ); |
46 |
is( $validator->option('replace_inline'), 0, 'Check option replace_inline' ); |
47 |
|
48 |
is( ref $validator->option( a => 1 ), 'Koha::Validator', 'Trying to set unknown option' ); |
49 |
is( $validator->option('a'), undef, 'Check unknown option again' ); |
50 |
|
51 |
$validator->option( a => 1, b => 2, replace_inline => 3, REPLACE_INLINE => 0 ); # last uc option incorrect |
52 |
is( $validator->option('replace_inline'), 1, 'Check option replace_inline after setting with hash' ); |
53 |
$validator->option( { a => 1, b => 2, break_on_error => 0, replace_inline => undef } ); |
54 |
is( $validator->option('replace_inline'), 0, 'Check option replace_inline after setting with hashref' ); |
55 |
is( $validator->option( reset => 1 )->option('break_on_error'), 1, 'break_on_error has been reset' ); |
56 |
throws_ok { $validator->option( a => 1, b => 2, 3 ) } 'Koha::Exceptions::BadParameter', 'Bad number of parameters'; |
57 |
}; |
58 |
|
59 |
subtest 'load' => sub { |
60 |
plan tests => 15; |
61 |
|
62 |
throws_ok { $validator->load } 'Koha::Exceptions::BadParameter', |
63 |
'No input for load'; |
64 |
throws_ok { $validator->load('wrong') } 'Koha::Exceptions::BadParameter', |
65 |
'Wrong input for load'; |
66 |
throws_ok { $validator->load( { defaultrule => undef } ) } 'Koha::Exceptions::BadParameter', |
67 |
'Rule should not be undefined'; |
68 |
throws_ok { $validator->load( { defaultrule => 'test' } ) } 'Koha::Exceptions::BadParameter', |
69 |
'The alias \'test\' cannot be resolved'; |
70 |
|
71 |
$validator->option( throw_exception => 0, allow_missing_rules => 1 ); |
72 |
|
73 |
is( ref $validator->load( { defaultrule => {} } ), 'Koha::Validator', 'Load returned object' ); |
74 |
ok( $validator->check( { 1 => 2 } ), 'Checked hash with empty defaultrule' ); |
75 |
ok( $validator->check( [ 1, 2 ] ), 'Checked array with empty defaultrule' ); |
76 |
$validator->load( { defaultrule => { code => sub { 1 } } } ); |
77 |
ok( $validator->check( { 1 => 2 } ), 'Checked hash with true sub' ); |
78 |
ok( $validator->check( [ 1, 2 ] ), 'Checked array with true sub' ); |
79 |
$validator->load( { defaultrule => { code => sub { } } } ); |
80 |
ok( !$validator->check( { 1 => 2 } ), 'Checked hash with false sub' ); |
81 |
ok( !$validator->check( [ 1, 2 ] ), 'Checked array with false sub' ); |
82 |
|
83 |
$validator->unload->load( { my_column => { values => [ 2, 3 ] } } ); |
84 |
ok( $validator->check( [1] ), 'Checked array without defaultrule' ); |
85 |
ok( $validator->check( { my_column => 3 } ), 'Checked hash with single rule' ); |
86 |
|
87 |
# Add standard rules, overwrite |
88 |
$Koha::Validator::standard_rules = { rule1 => { regexp => qr/\D/ }, rule2 => { defaultvalue => 1 } }; |
89 |
ok( $validator->unload->check( { rule1 => '2a' } ), 'Standard rule applied' ); |
90 |
$validator->load( { rule1 => { postfilter => sub { 2 } } } ); |
91 |
is_deeply( |
92 |
$validator->check( { rule1 => '2a', rule2 => undef } ), { rule1 => 2, rule2 => 1 }, |
93 |
'Rule1 overwritten' |
94 |
); |
95 |
$Koha::Validator::standard_rules = {}; |
96 |
|
97 |
$validator->unload->option( reset => 1 ); |
98 |
}; |
99 |
|
100 |
subtest 'check' => sub { |
101 |
plan tests => 19; |
102 |
|
103 |
throws_ok { $validator->check } 'Koha::Exceptions::BadParameter', 'No values to check'; |
104 |
throws_ok { $validator->check(1) } 'Koha::Exceptions::BadParameter', 'Check does only accept hashref or arrayref'; |
105 |
ok( $validator->check( {} ), 'Empty hash validated' ); |
106 |
ok( $validator->check( [] ), 'Empty list validated' ); |
107 |
|
108 |
subtest 'arrayref' => sub { |
109 |
$validator->load( { defaultrule => { values => [ 1, 2, 3 ] } } )->option( throw_exception => 0 ); |
110 |
ok( $validator->check( [ 1, 2, '2', 3, '1' ] ), 'Check array of integers and characters with defaultrule' ); |
111 |
|
112 |
my $code = sub { |
113 |
my ( $value, $index ) = @_; |
114 |
return ( $value == 2 or $value == 3 ) if $index == 0; |
115 |
return $value == 2 if $index == 1; |
116 |
return $value =~ qr/test/ if $index == 2; |
117 |
return 0 if $index == 3; |
118 |
return 0; |
119 |
}; |
120 |
$validator->load( { defaultrule => { ref => '', code => $code } } ); |
121 |
|
122 |
ok( !$validator->check( [ 1, ] ), 'Checked array with one element' ); |
123 |
is( $validator->errors(0), 'CODE', 'Check error' ); |
124 |
ok( $validator->check( [ 3, ] ), 'Checked another array with one element' ); |
125 |
ok( !$validator->check( [ [1] ] ), 'Checked another array with one nested arrayref' ); |
126 |
ok( !$validator->check( [ 2, 3 ] ), 'Checked array with two elements, fallback fails on index 1' ); |
127 |
is( $validator->errors(1), 'CODE', 'Check error for two elements array' ); |
128 |
ok( $validator->check( [ 2, '2', 'testcase' ] ), 'Checked array with three elements' ); |
129 |
ok( !$validator->check( [ 2, '2', 'textcase' ] ), 'Checked another array with three elements' ); |
130 |
is( $validator->errors(2), 'CODE', 'Check error for three elements array' ); |
131 |
ok( !$validator->check( [ 2, '2', 'testcase', 'index3' ] ), 'Checked array with four elements' ); |
132 |
is( $validator->errors(3), 'CODE', 'Check error for four elements array' ); |
133 |
|
134 |
$validator->unload->option( reset => 1 ); |
135 |
}; |
136 |
|
137 |
subtest 'hashref' => sub { |
138 |
my $rules = { |
139 |
digits_only => { regexp => qr/^\d+$/ }, |
140 |
email => { code => sub { Koha::Email->is_valid( $_[0] ) } }, |
141 |
test01 => 'digits_only', |
142 |
test02 => { values => [ 1, 2 ] }, |
143 |
test03 => { code => sub { 0 } }, |
144 |
test04 => 'email', |
145 |
test05 => { regexp => qr/test/ }, |
146 |
}; |
147 |
$validator->load($rules); |
148 |
my $data = { test01 => 2, test02 => 1, test04 => 'marcel@test.nl', test05 => 'includes test word' }; |
149 |
try { $validator->check($data); ok( 1, 'validator green' ) } |
150 |
catch { ok(0) }; |
151 |
is( $validator->errors, 0, 'No errors encountered' ); |
152 |
|
153 |
$data = { test01 => 2, test02 => 1, test03 => 'nomatterwhat' }; |
154 |
try { $validator->check($data); ok(0) } |
155 |
catch { ok( 1, 'validator red' ) }; |
156 |
is( $validator->errors('test03'), 'CODE', 'Check error code for test03' ); |
157 |
|
158 |
my $code = sub { return Koha::Email->is_valid( $_[0] ) && $_[0] !~ /[\`]/ }; |
159 |
$validator->load( { defaultrule => 'email', email => { code => $code } } ); |
160 |
$data = { email => 'withbacktick`ls`@test.com' }; |
161 |
try { $validator->check($data); ok(0) } |
162 |
catch { is( $validator->errors('email'), 'CODE', 'validator red for email with backticks' ) }; |
163 |
ok( $validator->check( { emailpro => 'john@doe.com' } ), 'John Doe never fails' ); |
164 |
$validator->unload->option( reset => 1 ); |
165 |
}; |
166 |
|
167 |
subtest 'mandatory' => sub { |
168 |
$validator->option( throw_exception => 0 ); |
169 |
$validator->load( { test => { mandatory => 1 }, test2 => {}, test3 => { mandatory => 0 } } ); |
170 |
ok( !$validator->check( {} ), 'Missing test column' ); |
171 |
is( $validator->errors('test'), 'MANDATORY', 'Check field' ); |
172 |
ok( $validator->check( { test => 1, test2 => 0 } ), 'only test should be present' ); |
173 |
$validator->unload->option( reset => 1 ); |
174 |
}; |
175 |
|
176 |
subtest 'ref' => sub { |
177 |
$validator->option( break_on_error => 0, throw_exception => 0 ); |
178 |
$validator->load( { a => { ref => q{} }, b => { ref => 'HASH' }, c => {}, d => { ref => [1] } } ); |
179 |
ok( !$validator->check( { a => [], b => q{}, c => 1 } ), 'a and b should fail' ); |
180 |
is_deeply( $validator->errors( code => 'REF' ), [ 'a', 'b' ], 'Check wrong fields' ); |
181 |
ok( !$validator->check( { d => 1 } ), 'Wrong ref definition, scalar expected' ); |
182 |
$validator->unload->option( reset => 1 ); |
183 |
}; |
184 |
|
185 |
subtest 'defaultvalue' => sub { |
186 |
$validator->load( { defaultrule => { defaultvalue => 1 } } ); |
187 |
is_deeply( $validator->check( [ undef, q{}, 0, 2 ] ), [ 1, q{}, 0, 2 ], 'defaultvalue on array' ); |
188 |
is_deeply( $validator->check( { a => undef, b => q{} } ), { a => 1, b => q{} }, 'defaultvalue on hash' ); |
189 |
$validator->unload; |
190 |
}; |
191 |
|
192 |
subtest 'prefilter' => sub { |
193 |
$validator->load( { test => { prefilter => sub { $_[0] =~ s/\s//g; $_[0] } } } ); |
194 |
is_deeply( $validator->check( { test => 'a b c ' } ), { test => 'abc' }, 'Prefilter removed spaces' ); |
195 |
$validator->load( { test => { prefilter => undef } } ); |
196 |
throws_ok { $validator->check( { test => 'a b c ' } ) } 'Koha::Exceptions::BadParameter', |
197 |
'Wrong prefilter, no coderef'; |
198 |
$validator->unload; |
199 |
}; |
200 |
|
201 |
subtest 'values' => sub { |
202 |
$validator->option( break_on_error => 0, throw_exception => 0 ); |
203 |
|
204 |
# Note: values for a and b are bad definitions! |
205 |
$validator->load( { a => { values => undef }, b => { values => 1 }, c => { values => [ 10, 20 ] } } ); |
206 |
$validator->check( { a => 1, b => 1, c => 1 } ); |
207 |
is_deeply( $validator->errors( code => 'VALUES' ), [ 'a', 'b', 'c' ], 'a, b and c failed' ); |
208 |
$validator->unload; |
209 |
$validator->load( { defaultrule => { values => [ 1, 2 ] }, c => { values => [3] } } ); |
210 |
ok( $validator->check( { a => 1, b => 2, c => 3 } ), 'All passed now' ); |
211 |
|
212 |
$validator->unload->option( reset => 1 ); |
213 |
}; |
214 |
|
215 |
subtest 'regexp' => sub { |
216 |
$validator->option( break_on_error => 0 ); |
217 |
$validator->load( { defaultrule => { regexp => '/test/i' } } ); # bad defined |
218 |
try { $validator->check( [1] ); ok(0) } |
219 |
catch { ok( 1, 'Bad regex definition' ) }; |
220 |
$validator->load( { defaultrule => { regexp => qr/\d\D/ } } ); |
221 |
try { $validator->check( [ '123', '1b', 'ab2' ] ); ok(0) } |
222 |
catch { ok( 1, 'Should fail on some elements' ) }; |
223 |
is_deeply( $validator->errors( code => 'REGEXP' ), [ 0, 2 ], 'Two elements failed' ); |
224 |
$validator->unload->option( reset => 1 ); |
225 |
}; |
226 |
|
227 |
subtest 'code' => sub { |
228 |
$validator->load( { test1 => { code => undef }, test2 => { code => 1 } } ); # bad defined |
229 |
try { $validator->check( { test1 => 1 } ); ok(0) } |
230 |
catch { ok( 1, 'Should fail on code undef' ) }; |
231 |
try { $validator->check( { test2 => 2 } ); ok(0) } |
232 |
catch { ok( 1, 'Should fail on code == 1' ) }; |
233 |
$validator->load( |
234 |
{ defaultrule => { code => sub { $_[0] =~ /a/ } }, test2 => { code => sub { $_[0] =~ /b/ } } } ); |
235 |
my $res = $validator->check( { test => 'Aa', test2 => 'bB' } ); |
236 |
$validator->unload; |
237 |
}; |
238 |
|
239 |
subtest 'postfilter' => sub { |
240 |
$validator->option( throw_exception => 0 ); |
241 |
$validator->load( |
242 |
{ |
243 |
defaultrule => { |
244 |
prefilter => sub { $_[0] =~ s/\s//g; $_[0] }, |
245 |
postfilter => sub { uc $_[0] }, |
246 |
}, |
247 |
test => {}, |
248 |
test3 => { |
249 |
postfilter => sub { die }, |
250 |
}, |
251 |
} |
252 |
); |
253 |
my $results = $validator->check( { test => 'a b c ', test2 => 'd e' } ); |
254 |
is( $results->{test}, 'a b c ', 'Field \'test\' does not hit postfilter' ); |
255 |
is( $results->{test2}, 'DE', 'Test2 passed postfilter of defaultrule' ); |
256 |
|
257 |
# what if postfilter crashes |
258 |
ok( !$validator->check( { test => 'a b c ', test2 => 'd e', test3 => 'f g' } ), 'Should stumble over die' ); |
259 |
is( $validator->errors('test3'), 'POSTFILTER', 'Check which field did not validate' ); |
260 |
|
261 |
$validator->unload->option( reset => 1 ); |
262 |
}; |
263 |
|
264 |
subtest 'allow_missing_rules' => sub { |
265 |
$validator->option( allow_missing_rules => 1, throw_exception => 0 ); |
266 |
$validator->load( { test => { regexp => qr/\d/ } } ); |
267 |
ok( $validator->check( { test => 2 } ), 'Validated' ); |
268 |
ok( $validator->check( { tezt => 3 } ), 'No rule for tezt is allowed here' ); |
269 |
$validator->option( allow_missing_rules => 0 ); |
270 |
ok( !$validator->check( { tezt => 3 } ), 'No rule for tezt no longer allowed' ); |
271 |
$validator->unload->option( reset => 1 ); |
272 |
}; |
273 |
|
274 |
subtest 'break_on_error' => sub { |
275 |
$validator->option( throw_exception => 0 ); |
276 |
$validator->load( { a => { values => [ 1, 2 ] }, b => { values => [ 2, 3 ] } } ); |
277 |
ok( !$validator->check( { a => 0, b => 0 } ), 'Wrong values for a and b' ); |
278 |
is_deeply( $validator->errors( array => 1 ), ['a'], 'Stopped when a was wrong' ); |
279 |
$validator->option( break_on_error => 0 ); |
280 |
ok( !$validator->check( { a => 0, b => 0 } ), 'Check a and b again' ); |
281 |
is_deeply( $validator->errors( array => 1 ), [ 'a', 'b' ], 'Found a and b now' ); |
282 |
$validator->unload->option( reset => 1 ); |
283 |
}; |
284 |
|
285 |
subtest 'replace_inline' => sub { |
286 |
$validator->option( replace_inline => 0, throw_exception => 0 ); |
287 |
$validator->load( { defaultrule => { ref => q{}, postfilter => sub { $_[0] + 1 } } } ); |
288 |
|
289 |
my $array = [ 1, 2 ]; |
290 |
my $hash = { a => 1, b => 2 }; |
291 |
is_deeply( $validator->check($array), [ 2, 3 ], 'Check results array' ); |
292 |
is_deeply( $validator->check($hash), { a => 2, b => 3 }, 'Check results hash' ); |
293 |
is_deeply( $array, [ 1, 2 ], 'Original array untouched' ); |
294 |
is_deeply( $hash, { a => 1, b => 2 }, 'Original hash untouched' ); |
295 |
|
296 |
$validator->option( replace_inline => 1 ); |
297 |
|
298 |
# First, add an invalid argument (reference); check and test if not modified |
299 |
$array = [ 1, 2, {} ]; |
300 |
$hash = { a => 1, b => 2, c => [] }; |
301 |
ok( !$validator->check($array), 'Array does no longer validate' ); |
302 |
ok( !$validator->check($hash), 'Hash does no longer validate' ); |
303 |
is_deeply( $array, [ 1, 2, {} ], 'Original array untouched' ); |
304 |
is_deeply( $hash, { a => 1, b => 2, c => [] }, 'Original hash untouched' ); |
305 |
|
306 |
# Pass valid content, and test if modified |
307 |
$array = [ 1, 2 ]; |
308 |
$hash = { a => 1, b => 2 }; |
309 |
$validator->check($array); |
310 |
$validator->check($hash); |
311 |
is_deeply( $array, [ 2, 3 ], 'Original array modified' ); |
312 |
is_deeply( $hash, { a => 2, b => 3 }, 'Original hash modified' ); |
313 |
|
314 |
$validator->unload->option( reset => 1 ); |
315 |
}; |
316 |
|
317 |
subtest 'restrict_postfilter' => sub { |
318 |
# Example of 3 fields, a == b + c; note that validations starts with a.. |
319 |
# Postfilter can only validate by throwing an exception. |
320 |
my $code = sub { my ( $val, $idx, $res ) = @_; die unless $res->{a} == $res->{b} + $val; $val }; |
321 |
$validator->load( { a => {}, b => {}, c => { postfilter => $code } } ); |
322 |
ok( $validator->check( { a => 3, b => 2, c => 1 } ), 'Passes' ); |
323 |
try { $validator->check( { a => 4, b => 2, c => 1 } ); ok(0) } |
324 |
catch { ok( 1, 'Should fail' ) }; |
325 |
|
326 |
# Now try to make postfilter correct/enforce the value of a (restrict_postfilter on) |
327 |
$code = sub { my ( $val, $idx, $res ) = @_; $res->{a} = $res->{b} + $val; $val }; |
328 |
$validator->load( { a => {}, b => {}, c => { postfilter => $code } } ); |
329 |
my $res = $validator->check( { a => 4, b => 2, c => 1 } ); |
330 |
is( $res->{a}, 4, 'Postfilter failed to adjust the value of a' ); |
331 |
# Correct attempt: restrict_postfilter off |
332 |
$validator->option( restrict_postfilter => 0 ); |
333 |
$res = $validator->check( { a => 4, b => 2, c => 1 } ); |
334 |
is( $res->{a}, 3, 'Postfilter did adjust the value of a, restrict_postfilter off' ); |
335 |
|
336 |
$validator->unload->option( reset => 1 ); |
337 |
}; |
338 |
|
339 |
subtest 'throw_exception' => sub { |
340 |
try { $validator->check( [1] ); ok(0) } |
341 |
catch { ok( 1, 'Exception for array without defaultrule' ) }; |
342 |
$validator->option( throw_exception => 0 ); |
343 |
my $res; |
344 |
lives_ok { $res = $validator->check( [1] ) } 'No exception for same check'; |
345 |
is( $res, undef, 'Returned undef' ); |
346 |
$validator->unload->option( reset => 1 ); |
347 |
}; |
348 |
}; |
349 |
|
350 |
subtest 'errors' => sub { |
351 |
plan tests => 14; |
352 |
$validator->option( throw_exception => 0 ); |
353 |
$validator->load( |
354 |
{ |
355 |
defaultrule => { ref => q{}, values => [ 1, 2 ], regexp => qr/[3-9]/ }, |
356 |
test => { mandatory => 1, code => sub { } } |
357 |
} |
358 |
); |
359 |
ok( !$validator->check( [ 0, {}, 2 ] ), 'Three elements should fail' ); |
360 |
is( $validator->errors, 1, 'Check error count, break on' ); |
361 |
is( $validator->errors(0), 'VALUES', 'Check error of index 0, break on' ); |
362 |
is_deeply( $validator->errors( hash => 1 ), { 0 => 'VALUES' }, 'Check hash of errors, break on' ); |
363 |
is_deeply( $validator->errors( list => 1 ), [0], 'Check list of errors, break on' ); |
364 |
is_deeply( $validator->errors( code => 'REF' ), [], 'Check list of errors for REF, break on' ); |
365 |
is_deeply( $validator->errors( code => 'VALUES' ), [0], 'Check list of errors for VALUES, break on' ); |
366 |
|
367 |
$validator->option( break_on_error => 0 ); |
368 |
ok( !$validator->check( [ 0, {}, 2 ] ), 'Three elements should fail again' ); |
369 |
is( $validator->errors, 3, 'Check error count, break off' ); |
370 |
is( $validator->errors(2), 'REGEXP', 'Check error of index 2, break off' ); |
371 |
is_deeply( |
372 |
$validator->errors( hash => 1 ), { 0 => 'VALUES', 1 => 'REF', 2 => 'REGEXP' }, |
373 |
'Check hash of errors, break off' |
374 |
); |
375 |
is_deeply( $validator->errors( array => 1 ), [ 0, 1, 2 ], 'Check array of errors, break off' ); |
376 |
is_deeply( $validator->errors( code => 'REF' ), [1], 'Check list of errors for REF, break off' ); |
377 |
is_deeply( $validator->errors( code => 'CODE' ), [], 'Check list of errors for CODE, break off' ); |
378 |
$validator->unload->option( reset => 1 ); |
379 |
}; |