File: test_iban.py

package info (click to toggle)
python-schwifty 2024.09.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,072 kB
  • sloc: python: 3,057; makefile: 209; sh: 9
file content (435 lines) | stat: -rw-r--r-- 16,571 bytes parent folder | download | duplicates (2)
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