1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
|
from __future__ import annotations
import copy
import pytest
from pycountry import countries # type: ignore
from schwifty import IBAN
from schwifty.exceptions import SchwiftyException
from schwifty.iban import convert_bban_spec_to_regex
valid = [
"AL47 2121 1009 0000 0002 3569 8741", # Albania
"AD12 0001 2030 2003 5910 0100", # Andorra
"AT61 1904 3002 3457 3201", # Austria
"AZ21 NABZ 0000 0000 1370 1000 1944", # Republic of Azerbaijan
"BH67 BMAG 0000 1299 1234 56", # Bahrain (Kingdom of)
"BE68 5390 0754 7034", # Belgium
"BA39 1290 0794 0102 8494", # Bosnia and Herzegovina
"BR97 0036 0305 0000 1000 9795 493P 1", # Brazil
"BR18 0000 0000 1414 5512 3924 100C 2", # Brazil
"BG80 BNBG 9661 1020 3456 78", # Bulgaria
"CR05 0152 0200 1026 2840 66", # Costa Rica
"HR12 1001 0051 8630 0016 0", # Croatia
"CY17 0020 0128 0000 0012 0052 7600", # Cyprus
"CZ65 0800 0000 1920 0014 5399", # Czech Republic
"CZ94 5500 0000 0010 1103 8930", # Czech Republic
"DK50 0040 0440 1162 43", # Greenland
"FO62 6460 0001 6316 34", # Faroer
"GL89 6471 0001 0002 06", # Denmark
"DO28 BAGR 0000 0001 2124 5361 1324", # Dominican Republic
"EE38 2200 2210 2014 5685", # Estonia
"FI21 1234 5600 0007 85", # Finland
"FR14 2004 1010 0505 0001 3M02 606", # France
"GE29 NB00 0000 0101 9049 17", # Georgia
"DE89 3704 0044 0532 0130 00", # Germany
"GI75 NWBK 0000 0000 7099 453", # Gibraltar
"GR16 0110 1250 0000 0001 2300 695", # Greece
"GT82 TRAJ 0102 0000 0012 1002 9690", # Guatemala
"HU42 1177 3016 1111 1018 0000 0000", # Hungary
"IS14 0159 2600 7654 5510 7303 39", # Iceland
"IE29 AIBK 9311 5212 3456 78", # Ireland
"IL62 0108 0000 0009 9999 999", # Israel
"IT60 X054 2811 1010 0000 0123 456", # Italy
"JO94 CBJO 0010 0000 0000 0131 0003 02", # Jordan
"KZ86 125K ZT50 0410 0100", # Kazakhstan
"XK05 1212 0123 4567 8906", # Republic of Kosovo
"KW81 CBKU 0000 0000 0000 1234 5601 01", # Kuwait
"LV80 BANK 0000 4351 9500 1", # Latvia
"LB62 0999 0000 0001 0019 0122 9114", # Lebanon
"LI21 0881 0000 2324 013A A", # Liechtenstein (Principality of)
"LT12 1000 0111 0100 1000", # Lithuania
"LU28 0019 4006 4475 0000", # Luxembourg
"MK07 2501 2000 0058 984", # Macedonia, Former Yugoslav Republic of
"MT84 MALT 0110 0001 2345 MTLC AST0 01S", # Malta
"MR13 0002 0001 0100 0012 3456 753", # Mauritania
"MU17 BOMM 0101 1010 3030 0200 000M UR", # Mauritius
"MD24 AG00 0225 1000 1310 4168", # Moldova
"MC58 1122 2000 0101 2345 6789 030", # Monaco
"ME25 5050 0001 2345 6789 51", # Montenegro
"NL91 ABNA 0417 1643 00", # The Netherlands
"NO93 8601 1117 947", # Norway
"PK36 SCBL 0000 0011 2345 6702", # Pakistan
"PS92 PALS 0000 0000 0400 1234 5670 2", # Palestine, State of
"PL61 1090 1014 0000 0712 1981 2874", # Poland
"PT50 0002 0123 1234 5678 9015 4", # Portugal
"QA58 DOHB 0000 1234 5678 90AB CDEF G", # Qatar
"RO49 AAAA 1B31 0075 9384 0000", # Romania
"LC55 HEMM 0001 0001 0012 0012 0002 3015", # Saint Lucia
"SM86 U032 2509 8000 0000 0270 100", # San Marino
"ST68 0001 0001 0051 8453 1011 2", # Sao Tome And Principe
"SA03 8000 0000 6080 1016 7519", # Saudi Arabia
"RS35 2600 0560 1001 6113 79", # Serbia
"SN08 SN01 0015 2000 0485 0000 3035", # Senegal
"SC18 SSCB 1101 0000 0000 0000 1497 USD", # Seychelles
"SK31 1200 0000 1987 4263 7541", # Slovak Republic
"SI56 1910 0000 0123 438", # Slovenia
"ES91 2100 0418 4502 0005 1332", # Spain
"SE45 5000 0000 0583 9825 7466", # Sweden
"CH93 0076 2011 6238 5295 7", # Switzerland
"TL38 0080 0123 4567 8910 157", # Timor-Leste
"TN59 1000 6035 1835 9847 8831", # Tunisia
"TR33 0006 1005 1978 6457 8413 26", # Turkey
"UA21 3996 2200 0002 6007 2335 6600 1", # Ukraine
"AE07 0331 2345 6789 0123 456", # United Arab Emirates
"GB29 NWBK 6016 1331 9268 19", # United Kingdom
"VG96 VPVG 0000 0123 4567 8901", # Virgin Islands, British
"BY13 NBRB 3600 9000 0000 2Z00 AB00", # Republic of Belarus
"SV62 CENR 0000 0000 0000 0070 0025", # El Salvador
"FO62 6460 0001 6316 34", # Faroe Islands
"GL89 6471 0001 0002 06", # Grenland
"IQ98 NBIQ 8501 2345 6789 012", # Iraq
]
invalid = [
"DE89 3704 0044 0532 0130", # Too short
"DE89 3704 0044 0532 0130 0000", # Too long
"GB96 BARC 2020 1530 0934 591", # Too long
"XX89 3704 0044 0532 0130 00", # Wrong country-code
"DE99 3704 0044 0532 0130 00", # Wrong check digits
"DEAA 3704 0044 0532 0130 00", # Wrong format (check digits)
"GB2L ABBY 0901 2857 2017 07", # Wrong format (check digits)
"DE89 AA04 0044 0532 0130 00", # Wrong format (country specific)
"GB12 BARC 2020 1530 093A 59", # Wrong account format (country specific)
"GB01 BARC 2071 4583 6083 87", # Wrong checksum digits
"GB00 HLFX 1101 6111 4553 65", # Wrong checksum digits
"GB94 BARC 2020 1530 0934 59", # Wrong checksum digits
]
experimental = [
"DZ58 0002 1000 0111 3000 0005 70", # Algeria
"AO06 0044 0000 6729 5030 1010 2", # Angola
"BJ66 BJ06 1010 0100 1443 9000 0769", # Benin
"BF42 BF08 4010 1300 4635 7400 0390", # Burkina Faso
"CM21 1000 2000 3002 7797 6315 008", # Cameroon
"CV64 0005 0000 0020 1082 1514 4", # Cape Verde
"CF42 2000 1000 0101 2006 9700 160", # Central African Republic
"TD89 6000 2000 0102 7109 1600 153", # Chad
"KM46 0000 5000 0100 1090 4400 137", # Comoros
"CG39 3001 1000 1010 1345 1300 019", # Congo
"GQ70 5000 2001 0037 1522 8190 196", # Equatorial Guinea
"GA21 4002 1010 0320 0189 0020 126", # Gabon
"GW04 GW14 3001 0181 8006 3760 1", # Guinea-Bissau
"HN54 PISA 0000 0000 0000 0012 3124", # Honduras
"IR71 0570 0299 7160 1460 6410 01", # Iran
"CI93 CI00 8011 1301 1342 9120 0589", # Ivory Coast
"MG46 0000 5030 0712 8942 1016 045", # Madagascar
"ML13 ML01 6012 0102 6001 0066 8497", # Mali
"MA64 0115 1900 0001 2050 0053 4921", # Morocco
"MZ59 0003 0108 0016 3671 0237 1", # Mozambique
"NE58 NE03 8010 0100 1303 0500 0268", # Niger
"SN08 SN01 0015 2000 0485 0000 3035", # Senegal
"TG53 TG00 9060 4310 3465 0040 0070", # Togo
]
sepa_countries = {
"AD",
"AT",
"BE",
"BG",
"CH",
"CY",
"CZ",
"DE",
"DK",
"EE",
"ES",
"FI",
"FR",
"GB",
"GI",
"GR",
"HR",
"HU",
"IE",
"IS",
"IT",
"LI",
"LT",
"LU",
"LV",
"MC",
"MT",
"NL",
"NO",
"PL",
"PT",
"RO",
"SE",
"SK",
"SI",
"SM",
"VA",
}
@pytest.mark.parametrize("value", valid)
def test_parse_iban(value: str) -> None:
iban = IBAN(value, validate_bban=True)
assert iban.formatted == value
assert iban.country == countries.get(alpha_2=iban.country_code)
assert iban.in_sepa_zone is (iban.country_code in sepa_countries)
@pytest.mark.parametrize("value", experimental)
def test_experimental_iban(value: str) -> None:
iban = IBAN(value, validate_bban=True)
assert iban.formatted == value
@pytest.mark.parametrize("value", invalid)
def test_parse_iban_allow_invalid(value: str) -> None:
iban = IBAN(value, allow_invalid=True)
with pytest.raises(SchwiftyException):
iban.validate()
@pytest.mark.parametrize("value", invalid)
def test_invalid_iban(value: str) -> None:
with pytest.raises(SchwiftyException):
IBAN(value)
def test_iban_properties_de() -> None:
iban = IBAN("DE42430609677000534100")
assert iban.is_valid is True
assert iban.bank_code == "43060967"
assert iban.branch_code == ""
assert iban.account_code == "7000534100"
assert iban.country_code == "DE"
assert iban.national_checksum_digits == ""
assert iban.bic == "GENODEM1GLS"
assert iban.formatted == "DE42 4306 0967 7000 5341 00"
assert iban.length == len(iban) == 22
assert iban.country == countries.get(alpha_2="DE")
assert iban.bank_name == "GLS Gemeinschaftsbank"
assert iban.bank_short_name == "GLS Gemeinschaftsbk Bochum"
assert iban.in_sepa_zone is True
def test_iban_properties_it() -> None:
iban = IBAN("IT60 X054 2811 1010 0000 0123 456")
assert iban.bank_code == "05428"
assert iban.branch_code == "11101"
assert iban.account_code == "000000123456"
assert iban.national_checksum_digits == "X"
assert iban.country == countries.get(alpha_2="IT")
assert iban.bic == "BLOPIT22"
assert iban.bank_name == "BANCA POPOLARE DI BERGAMO S.P.A."
assert iban.in_sepa_zone is True
@pytest.mark.parametrize(
("components", "compact"),
[
(("BE", "050", "123"), "BE66050000012343"),
(("BE", "050", "123456"), "BE45050012345689"),
(("BE", "539", "0075470"), "BE68539007547034"),
(("BE", "050", "177"), "BE54050000017797"),
(("DE", "43060967", "7000534100"), "DE42430609677000534100"),
(("DE", "51230800", "2622196545"), "DE61512308002622196545"),
(("DE", "20690500", "9027378"), "DE37206905000009027378"),
(("DK", "0040", "0440116243"), "DK5000400440116243"),
(("FR", "2004101005", "0500013M026"), "FR1420041010050500013M02606"),
(("GB", "NWBK", "31926819", "601613"), "GB29NWBK60161331926819"),
(("GB", "NWBK", "31926819"), "GB66NWBK00000031926819"),
(("GB", "NWBK601613", "31926819"), "GB29NWBK60161331926819"),
(("IT", "0538703601", "000000198036"), "IT18T0538703601000000198036"),
(("IT", "0538703601", "000000198060"), "IT57V0538703601000000198060"),
(("IT", "0538703601", "000000198072"), "IT40Z0538703601000000198072"),
(("IT", "0538742530", "000000802006"), "IT29P0538742530000000802006"),
(("IT", "0306940101", "100100003599"), "IT94I0306940101100100003599"),
(("IT", "0335901600", "100000131525"), "IT63M0335901600100000131525"),
(("IT", "03359", "100000131525", "01600"), "IT63M0335901600100000131525"),
(("IT", "39189", "CHTEE9UATVVO", "13896"), "IT12D3918913896CHTEE9UATVVO"),
(("IT", "49076", "YB4EQZ0PL4GT", "75919"), "IT70Y4907675919YB4EQZ0PL4GT"),
(("IT", "05428", "00ABCD12ZE34", "01600"), "IT21Q054280160000ABCD12ZE34"),
(("IT", "05428", "000000123456", "11101"), "IT60X0542811101000000123456"),
(("IT", "55354", "AUKNAEXQVZOG", "87408"), "IT77D5535487408AUKNAEXQVZOG"),
(("IT", "31582", "YMZJIILHBUN0", "80362"), "IT49B3158280362YMZJIILHBUN0"),
(("IT", "39814", "LSLLPTLPK716", "69135"), "IT37W3981469135LSLLPTLPK716"),
(("IT", "86388", "3VVRNLLMMN9N", "85779"), "IT69U86388857793VVRNLLMMN9N"),
(("IT", "43482", "DNNDKPHAGTIB", "07900"), "IT22K4348207900DNNDKPHAGTIB"),
(("IT", "70000", "Mq8gyacBzEqP", "30810"), "IT39M7000030810MQ8GYACBZEQP"),
(("IT", "76494", "2Sbpqelox4wG", "16460"), "IT87A76494164602SBPQELOX4WG"),
],
)
def test_generate_iban(components: tuple[str, ...], compact: str) -> None:
iban = IBAN.generate(*components)
iban.validate(validate_bban=True)
assert iban.compact == compact
@pytest.mark.parametrize(
"components",
[
("DE", "012345678", "7000123456"),
("DE", "51230800", "01234567891"),
("GB", "NWBK", "31926819", "1234567"),
],
)
def test_generate_iban_invalid(components: tuple[str, ...]) -> None:
with pytest.raises(SchwiftyException):
IBAN.generate(*components)
def test_magic_methods() -> None:
iban = IBAN("DE42430609677000534100")
assert iban == "DE42430609677000534100"
assert iban == IBAN("DE42430609677000534100")
assert iban != IBAN("ES9121000418450200051332")
assert iban < IBAN("ES9121000418450200051332")
assert str(iban) == "DE42430609677000534100"
assert hash(iban) == hash("DE42430609677000534100")
assert repr(iban) == "<IBAN=DE42430609677000534100>"
@pytest.mark.parametrize(
("iban", "compact"),
[
("AD1200012030200359100100", "BACAADADXXX"),
("AE070331234567890123456", "BOMLAEADXXX"),
("AT483200000012345864", "RLNWATWWXXX"),
("AT930100000000123145", "BUNDATWWXXX"),
("BA393385804800211234", "UNCRBA22XXX"),
("BE71096123456769", "GKCCBEBB"),
("BG18RZBB91550123456789", "RZBBBGSF"),
("CH5604835012345678009", "CRESCHZZ80A"),
("CR23015108410026012345", "BNCRCRSJXXX"),
("CY21002001950000357001234567", "BCYPCY2N"),
("CZ5508000000001234567899", "GIBACZPX"),
("DE37206905000009027378", "GENODEF1S11"),
# ("DK9520000123456789", "NDEADKKK"),
("EE471000001020145685", "EEUHEE2X"),
("ES7921000813610123456789", "CAIXESBB"),
("FI1410093000123458", "NDEAFIHH"),
("FR7630006000011234567890189", "AGRIFRPPXXX"),
("GB33BUKB20201555555555", "BUKBGB22"),
("GR9608100010000001234567890", "BOFAGR2XXXX"),
("HR1723600001101234565", "ZABAHR2X"),
("HU42117730161111101800000000", "OTPVHUHB"),
("IE64IRCE92050112345678", "IRCEIE2DXXX"),
("IT60X0542811101000000123456", "BLOPIT22"),
# ("IL170108000000012612345", "LUMIILIT"),
("IS480114007083000000000000", "NBIIISRE"),
("KZ244350000012344567", "SHBKKZKA"),
("LT601010012345678901", "LIABLT2XXXX"),
("LU120010001234567891", "BCEELULL"),
("LV97HABA0012345678910", "HABALV22XXX"),
# ("MD21EX000000000001234567", "EXMMMD22"),
("NL02ABNA0123456789", "ABNANL2A"),
("NO8330001234567", "SPSONO22"),
("PL50860000020000000000093122", "POLUPLPRXXX"),
("PT50002700000001234567833", "BPIPPTPLXXX"),
("RO66BACX0000001234567890", "BACXROBU"),
("RS35105008123123123173", "AIKBRS22XXX"),
("SE7280000810340009783242", "SWEDSESS"),
("SI56192001234567892", "SZKBSI2XXXX"),
("SK8975000000000012345671", "CEKOSKBX"),
# ("UA903052992990004149123456789", "PBANUA2X"),
],
)
def test_bic_from_iban(iban: str, compact: str) -> None:
bic = IBAN(iban).bic
assert bic is not None
assert bic.compact == compact
def test_unknown_bic_from_iban() -> None:
assert IBAN("SI72000001234567892").bic is None
def test_unknown_bank_name_from_iban() -> None:
assert IBAN("SI72000001234567892").bank_name is None
def test_unknown_bank_name_short_from_iban() -> None:
assert IBAN("SI72000001234567892").bank_short_name is None
def test_random_iban() -> None:
for _ in range(100):
iban = IBAN.random()
assert isinstance(iban, IBAN)
def test_random_special_cases() -> None:
iban = IBAN.random(country_code="MU")
assert iban.endswith("000MUR")
iban = IBAN.random(country_code="SC")
assert iban.endswith("SCR")
iban = IBAN.random(country_code="KM")
assert iban.is_valid
def test_pydantic_protocol() -> None:
from pydantic import BaseModel
from pydantic import ValidationError
class Model(BaseModel):
iban: IBAN
model = Model(iban="GL89 6471 0001 0002 06") # type: ignore[arg-type]
assert isinstance(model.iban, IBAN)
assert model.model_dump() == {"iban": model.iban}
with pytest.raises(ValidationError) as err:
Model(iban="GB00 HLFX 1101 6111 4553 65") # type: ignore[arg-type]
assert len(err.value.errors()) == 1
error = err.value.errors()[0]
assert error["type"] == "iban_format"
assert error["msg"] == "Invalid checksum digits"
assert error["input"] == "GB00 HLFX 1101 6111 4553 65"
json_schema = model.model_json_schema()
assert json_schema["properties"]["iban"] == {"maxLength": 34, "title": "IBAN", "type": "string"}
dumped = model.model_dump_json()
assert dumped == '{"iban":"GL8964710001000206"}'
loaded = Model.model_validate_json(dumped)
assert loaded == model
@pytest.mark.parametrize(
("spec", "regex"),
[
("5!n", r"^\d{5}$"),
("4!a", r"^[A-Z]{4}$"),
("10!c", r"^[A-Za-z0-9]{10}$"),
("5!e", r"^ {5}$"),
("3n", r"^\d{1,3}$"),
("5!n3!a", r"^\d{5}[A-Z]{3}$"),
],
)
def test_convert_bban_spec_to_regex(spec: str, regex: str) -> None:
assert convert_bban_spec_to_regex(spec) == regex
def test_copy() -> None:
iban = IBAN("BE68 5390 0754 7034")
iban_deepcopy = copy.deepcopy(iban)
assert id(iban) != id(iban_deepcopy)
assert iban == iban_deepcopy
iban_copy = copy.copy(iban)
assert id(iban) != id(iban_copy)
assert iban == iban_copy
|