1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.validator.routines;
18
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import static org.hamcrest.core.Is.is;
21 import static org.hamcrest.core.IsEqual.equalTo;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import static org.junit.jupiter.api.Assertions.fail;
28
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.InputStreamReader;
32 import java.io.Reader;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36 import org.apache.commons.csv.CSVFormat;
37 import org.apache.commons.csv.CSVParser;
38 import org.apache.commons.csv.CSVRecord;
39 import org.apache.commons.validator.routines.IBANValidator.Validator;
40 import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit;
41 import org.junit.jupiter.api.Test;
42
43
44
45
46
47
48 public class IBANValidatorTest {
49
50 private static final IBANValidator VALIDATOR = IBANValidator.getInstance();
51
52
53
54 private static final String IBAN_PART = "(?:(\\d+)!([acn]))";
55
56 private static final Pattern IBAN_PAT = Pattern
57 .compile("([A-Z]{2})" + IBAN_PART + IBAN_PART + IBAN_PART + IBAN_PART + "?" + IBAN_PART + "?" + IBAN_PART + "?" + IBAN_PART + "?");
58
59
60
61
62
63 private static final String[] VALID_IBAN_FIXTURES = {
64 "AD1200012030200359100100",
65 "AE070331234567890123456",
66 "AL47212110090000000235698741",
67 "AT611904300234573201",
68 "AZ21NABZ00000000137010001944",
69 "BA391290079401028494",
70 "BE68539007547034",
71 "BG80BNBG96611020345678",
72 "BH67BMAG00001299123456",
73 "BI4210000100010000332045181",
74 "BR1800000000141455123924100C2",
75 "BR1800360305000010009795493C1",
76 "BR9700360305000010009795493P1",
77 "BY13NBRB3600900000002Z00AB00",
78 "CH9300762011623852957",
79 "CR05015202001026284066",
80 "CY17002001280000001200527600",
81 "CZ6508000000192000145399",
82 "CZ9455000000001011038930",
83 "DE89370400440532013000",
84 "DJ2110002010010409943020008",
85 "DK5000400440116243",
86 "DO28BAGR00000001212453611324",
87 "EE382200221020145685",
88 "EG380019000500000000263180002",
89 "ES9121000418450200051332",
90 "FI2112345600000785",
91 "FI5542345670000081",
92
93 "AX2112345600000785",
94 "AX5542345670000081",
95 "FO6264600001631634",
96 "FR1420041010050500013M02606",
97
98 "BL6820041010050500013M02606",
99 "GF4120041010050500013M02606",
100 "GP1120041010050500013M02606",
101 "MF8420041010050500013M02606",
102 "MQ5120041010050500013M02606",
103 "NC8420041010050500013M02606",
104 "PF5720041010050500013M02606",
105 "PM3620041010050500013M02606",
106 "RE4220041010050500013M02606",
107 "TF2120041010050500013M02606",
108 "WF9120041010050500013M02606",
109 "YT3120041010050500013M02606",
110 "GB29NWBK60161331926819",
111
112
113
114
115 "GE29NB0000000101904917",
116 "GI75NWBK000000007099453",
117 "GL8964710001000206",
118 "GR1601101250000000012300695",
119 "GT82TRAJ01020000001210029690",
120 "HR1210010051863000160",
121 "HU42117730161111101800000000",
122 "IE29AIBK93115212345678",
123 "IL620108000000099999999",
124 "IQ98NBIQ850123456789012",
125 "IS140159260076545510730339",
126 "IT60X0542811101000000123456",
127 "JO94CBJO0010000000000131000302",
128 "KW81CBKU0000000000001234560101",
129 "KZ86125KZT5004100100",
130 "LB62099900000001001901229114",
131 "LC55HEMM000100010012001200023015",
132 "LI21088100002324013AA",
133 "LT121000011101001000",
134 "LU280019400644750000",
135 "LY83002048000020100120361",
136 "LV80BANK0000435195001",
137 "LY83002048000020100120361",
138 "MC5811222000010123456789030",
139 "MD24AG000225100013104168",
140 "ME25505000012345678951",
141 "MK07250120000058984",
142 "MR1300020001010000123456753",
143 "MT84MALT011000012345MTLCAST001S",
144 "MU17BOMM0101101030300200000MUR",
145 "NL91ABNA0417164300",
146 "NO9386011117947",
147 "PK36SCBL0000001123456702",
148 "PL61109010140000071219812874",
149 "PS92PALS000000000400123456702",
150 "PT50000201231234567890154",
151 "QA58DOHB00001234567890ABCDEFG",
152 "RO49AAAA1B31007593840000",
153 "RS35260005601001611379",
154 "RU0204452560040702810412345678901",
155 "SA0380000000608010167519",
156 "SC18SSCB11010000000000001497USD",
157 "SD8811123456789012",
158 "SE4550000000058398257466",
159 "SI56191000000123438",
160 "SI56263300012039086",
161 "SK3112000000198742637541",
162 "SM86U0322509800000000270100",
163 "ST68000100010051845310112",
164 "SV62CENR00000000000000700025",
165 "SV43ACAT00000000000000123123",
166 "TL380080012345678910157",
167 "TN5910006035183598478831",
168 "TR330006100519786457841326",
169 "UA213223130000026007233566001",
170 "UA213996220000026007233566001",
171 "VA59001123000012345678",
172 "VG96VPVG0000012345678901",
173 "XK051212012345678906",
174 };
175
176
177
178 private static final String[] INVALID_IBAN_FIXTURES = {
179 "",
180 " ",
181 "A",
182 "AB",
183 "FR1420041010050500013m02606",
184 "MT84MALT011000012345mtlcast001s",
185 "LI21088100002324013aa",
186 "QA58DOHB00001234567890abcdefg",
187 "RO49AAAA1b31007593840000",
188 "LC62HEMM000100010012001200023015",
189 "BY00NBRB3600000000000Z00AB00",
190 "ST68000200010192194210112",
191 "SV62CENR0000000000000700025",
192 };
193
194
195 private static int checkIBAN(final File file, final IBANValidator val) throws Exception {
196
197
198 final CSVFormat format = CSVFormat.DEFAULT.builder().setDelimiter('\t').build();
199 final Reader rdr = new InputStreamReader(new FileInputStream(file), "ISO_8859_1");
200 try (final CSVParser p = new CSVParser(rdr, format)) {
201 CSVRecord country = null;
202 CSVRecord cc = null;
203 CSVRecord structure = null;
204 CSVRecord length = null;
205 for (final CSVRecord o : p) {
206 final String item = o.get(0);
207 if ("Name of country".equals(item)) {
208 country = o;
209 } else if ("IBAN prefix country code (ISO 3166)".equals(item)) {
210 cc = o;
211 } else if ("IBAN structure".equals(item)) {
212 structure = o;
213 } else if ("IBAN length".equals(item)) {
214 length = o;
215 }
216 }
217 assertNotNull(country);
218 assertNotNull(length);
219 assertNotNull(structure);
220 assertNotNull(cc);
221 for (int i = 1; i < country.size(); i++) {
222 try {
223
224 final String newLength = length.get(i).split("!")[0];
225 final String newRE = fmtRE(structure.get(i), Integer.parseInt(newLength));
226 final Validator valre = val.getValidator(cc.get(i));
227 if (valre == null) {
228 System.out.println("// Missing entry:");
229 printEntry(cc.get(i), newLength, newRE, country.get(i));
230 } else {
231 final String currentLength = Integer.toString(valre.ibanLength);
232 final String currentRE = valre.getRegexValidator().toString().replaceAll("^.+?\\{(.+)}", "$1")
233
234 .replace("\\d", "\\\\d");
235
236 if (currentRE.equals(newRE) && currentLength.equals(newLength)) {
237
238 } else {
239 System.out.println("// Expected: " + newRE + ", " + newLength + " Actual: " + currentRE + ", " + currentLength);
240 printEntry(cc.get(i), newLength, newRE, country.get(i));
241 }
242
243 }
244
245 } catch (final IllegalArgumentException e) {
246 e.printStackTrace();
247 }
248 }
249 p.close();
250 return country.size();
251 }
252 }
253
254 private static String fmtRE(final String iban_pat, final int iban_len) {
255 final Matcher m = IBAN_PAT.matcher(iban_pat);
256 if (!m.matches()) {
257 throw new IllegalArgumentException("Unexpected IBAN pattern " + iban_pat);
258 }
259 final StringBuilder sb = new StringBuilder();
260 final String cc = m.group(1);
261 int totalLen = cc.length();
262 sb.append(cc);
263 int len = Integer.parseInt(m.group(2));
264 String curType = m.group(3);
265 for (int i = 4; i <= m.groupCount(); i += 2) {
266 if (m.group(i) == null) {
267 break;
268 }
269 final int count = Integer.parseInt(m.group(i));
270 final String type = m.group(i + 1);
271 if (type.equals(curType)) {
272 len += count;
273 } else {
274 sb.append(formatToRE(curType, len));
275 totalLen += len;
276 curType = type;
277 len = count;
278 }
279 }
280 sb.append(formatToRE(curType, len));
281 totalLen += len;
282 if (iban_len != totalLen) {
283 throw new IllegalArgumentException("IBAN pattern " + iban_pat + " does not match length " + iban_len);
284 }
285 return sb.toString();
286 }
287
288
289 private static String formatToRE(final String type, final int len) {
290 final char ctype = type.charAt(0);
291 switch (ctype) {
292 case 'n':
293 return String.format("\\\\d{%d}", len);
294 case 'a':
295 return String.format("[A-Z]{%d}", len);
296 case 'c':
297 return String.format("[A-Z0-9]{%d}", len);
298 default:
299 throw new IllegalArgumentException("Unexpected type " + type);
300 }
301 }
302
303 public static void main(final String[] a) throws Exception {
304 final IBANValidator validator = new IBANValidator();
305 final File iban_tsv = new File("target", "iban-registry.tsv");
306 int countries = 0;
307 if (iban_tsv.canRead()) {
308 countries = checkIBAN(iban_tsv, validator);
309 } else {
310 System.out.println("Please load the file " + iban_tsv.getCanonicalPath() + " from https://www.swift.com/standards/data-standards/iban");
311 }
312 System.out.println("Processed " + countries + " countries.");
313 }
314
315 private static void printEntry(final String ccode, final String length, final String ib, final String country) {
316 final String fmt = String.format("\"%s\"", ib);
317 System.out.printf(" new Validator(\"%s\", %s, %-40s), // %s\n", ccode, length, fmt, country);
318 }
319
320 @Test
321 public void testGetRegexValidatortPatterns() {
322 assertNotNull(VALIDATOR.getValidator("GB").getRegexValidator().getPatterns(), "GB");
323 }
324
325 @Test
326 public void testGetValidator() {
327 assertNotNull(VALIDATOR.getValidator("GB"), "GB");
328 assertNull(VALIDATOR.getValidator("gb"), "gb");
329 }
330
331 @Test
332 public void testHasValidator() {
333 assertTrue(VALIDATOR.hasValidator("GB"), "GB");
334 assertFalse(VALIDATOR.hasValidator("gb"), "gb");
335 }
336
337 @Test
338 public void testInValid() {
339 for (final String f : INVALID_IBAN_FIXTURES) {
340 assertFalse(VALIDATOR.isValid(f), f);
341 }
342 }
343
344 @Test
345 public void testNull() {
346 assertFalse(VALIDATOR.isValid(null), "isValid(null)");
347 }
348
349 @Test
350 public void testSetDefaultValidator1() {
351 final IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> VALIDATOR.setValidator("GB", 15, "GB"));
352 assertThat(thrown.getMessage(), is(equalTo("The singleton validator cannot be modified")));
353 }
354
355 @Test
356 public void testSetDefaultValidator2() {
357 final IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> VALIDATOR.setValidator("GB", -1, "GB"));
358 assertThat(thrown.getMessage(), is(equalTo("The singleton validator cannot be modified")));
359 }
360
361 @Test
362 public void testSetValidatorLC() {
363 final IBANValidator validator = new IBANValidator();
364 final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> validator.setValidator("gb", 15, "GB"));
365 assertThat(thrown.getMessage(), is(equalTo("Invalid country Code; must be exactly 2 upper-case characters")));
366 }
367
368 @Test
369 public void testSetValidatorLen_1() {
370 final IBANValidator validator = new IBANValidator();
371 assertNotNull(validator.setValidator("GB", -1, ""), "should be present");
372 assertNull(validator.setValidator("GB", -1, ""), "no longer present");
373 }
374
375 @Test
376 public void testSetValidatorLen35() {
377 final IBANValidator validator = new IBANValidator();
378 final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> validator.setValidator("GB", 35, "GB"));
379 assertThat(thrown.getMessage(), is(equalTo("Invalid length parameter, must be in range 8 to 34 inclusive: 35")));
380 }
381
382 @Test
383 public void testSetValidatorLen7() {
384 final IBANValidator validator = new IBANValidator();
385 final IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> validator.setValidator("GB", 7, "GB"));
386 assertThat(thrown.getMessage(), is(equalTo("Invalid length parameter, must be in range 8 to 34 inclusive: 7")));
387 }
388
389 @Test
390 public void testSorted() {
391 final IBANValidator validator = new IBANValidator();
392 final Validator[] vals = validator.getDefaultValidators();
393 assertNotNull(vals);
394 for (int i = 1; i < vals.length; i++) {
395 if (vals[i].countryCode.compareTo(vals[i - 1].countryCode) <= 0) {
396 fail("Not sorted: " + vals[i].countryCode + " <= " + vals[i - 1].countryCode);
397 }
398 }
399 }
400
401 @Test
402 public void testValid() {
403 for (final String f : VALID_IBAN_FIXTURES) {
404 assertTrue(IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(f), "Checksum fail: " + f);
405 assertTrue(VALIDATOR.hasValidator(f), "Missing validator: " + f);
406 assertTrue(VALIDATOR.isValid(f), f);
407 }
408 }
409 }