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