import pytest from engine.id_parser import ( validate_luhn, parse_id_number, extract_id_number, cross_validate, validate_id_for_country, _normalize_date, _check_date_transposition, ) class TestValidateLuhn: def test_valid_id(self): # Known valid SA ID number assert validate_luhn("8801015800082") is True def test_invalid_id_bad_checksum(self): assert validate_luhn("8801015800083") is False def test_invalid_too_short(self): assert validate_luhn("880101580008") is False def test_invalid_too_long(self): assert validate_luhn("88010158000820") is False def test_invalid_non_digits(self): assert validate_luhn("880101580008A") is False def test_empty_string(self): assert validate_luhn("") is False def test_none(self): assert validate_luhn(None) is False def test_valid_female_citizen(self): assert validate_luhn("9202204720083") is True def test_valid_male_resident(self): assert validate_luhn("7801015800182") is True class TestParseIdNumber: def test_male_citizen(self): result = parse_id_number("8801015800082") assert result["date_of_birth"] == "1988-01-01" assert result["sex"] == "Male" assert result["citizenship"] == "SA Citizen" assert result["is_valid"] is True def test_female(self): result = parse_id_number("9202204720083") assert result["sex"] == "Female" assert result["is_valid"] is True def test_permanent_resident(self): result = parse_id_number("7801015800182") assert result["citizenship"] == "Permanent Resident" def test_invalid_id(self): result = parse_id_number("1234567890123") assert result["is_valid"] is False def test_empty(self): result = parse_id_number("") assert result["is_valid"] is False assert result["date_of_birth"] is None class TestExtractIdNumber: def test_find_in_clean_text(self): blocks = [ ([[0, 0], [100, 0], [100, 20], [0, 20]], "IDENTITY NUMBER", 0.95), ([[0, 25], [200, 25], [200, 45], [0, 45]], "8801015800082", 0.92), ] id_num, conf = extract_id_number(blocks) assert id_num == "8801015800082" assert conf > 0 def test_find_with_spaces(self): blocks = [ ([[0, 0], [200, 0], [200, 20], [0, 20]], "8801 0158 0008 2", 0.85), ] id_num, conf = extract_id_number(blocks) assert id_num == "8801015800082" def test_find_with_dashes(self): blocks = [ ([[0, 0], [200, 0], [200, 20], [0, 20]], "880101-5800-082", 0.85), ] id_num, conf = extract_id_number(blocks) assert id_num == "8801015800082" def test_no_valid_id(self): blocks = [ ([[0, 0], [100, 0], [100, 20], [0, 20]], "REPUBLIC OF SOUTH AFRICA", 0.95), ([[0, 25], [100, 25], [100, 45], [0, 45]], "IDENTITY CARD", 0.90), ] id_num, conf = extract_id_number(blocks) assert id_num is None class TestCrossValidate: def test_all_match(self): fields = { "date_of_birth": "01/01/1988", "sex": "Male", "citizenship_status": "SA Citizen", } parsed = { "date_of_birth": "1988-01-01", "sex": "Male", "citizenship": "SA Citizen", "is_valid": True, } result = cross_validate(fields, parsed) assert result["luhn_valid"] is True assert result["dob_cross_check"] is True assert result["gender_cross_check"] is True assert result["citizenship_cross_check"] is True def test_gender_mismatch(self): fields = {"sex": "Female"} parsed = {"sex": "Male", "is_valid": True} result = cross_validate(fields, parsed) assert result["gender_cross_check"] is False class TestNormalizeDate: def test_iso_format(self): assert _normalize_date("1988-01-01") == "1988-01-01" def test_slash_format(self): assert _normalize_date("01/01/1988") == "1988-01-01" def test_dash_dmy(self): assert _normalize_date("01-01-1988") == "1988-01-01" def test_invalid(self): assert _normalize_date("not a date") is None class TestDateTransposition: def test_exact_match(self): result = _check_date_transposition("01/01/1988", "1988-01-01") assert result["exact_match"] is True assert result["transposed_match"] is False def test_transposed_day_month(self): # OCR reads "1988-05-03" but ID actually encodes 1988-03-05 (day/month swapped) result = _check_date_transposition("1988-05-03", "1988-03-05") assert result["transposed_match"] is True assert result["corrected_date"] == "1988-03-05" def test_no_match(self): result = _check_date_transposition("15/06/1990", "1988-01-01") assert result["exact_match"] is False assert result["transposed_match"] is False def test_none_input(self): result = _check_date_transposition(None, "1988-01-01") assert result["exact_match"] is False def test_unambiguous_date_not_transposed(self): # Day=25 can't be a month, so transposition shouldn't match result = _check_date_transposition("25/06/1990", "1990-06-25") assert result["exact_match"] is True class TestValidateIdForCountry: def test_sa_valid(self): result = validate_id_for_country("8801015800082", "ZAF") assert result["valid"] is True assert result["method"] == "luhn" def test_sa_invalid_luhn(self): result = validate_id_for_country("8801015800083", "ZAF") assert result["valid"] is False def test_nigeria_valid(self): result = validate_id_for_country("12345678901", "NGA") assert result["valid"] is True assert result["method"] == "regex" def test_nigeria_invalid_too_short(self): result = validate_id_for_country("1234567890", "NGA") assert result["valid"] is False def test_kenya_valid(self): result = validate_id_for_country("12345678", "KEN") assert result["valid"] is True def test_zimbabwe_valid(self): result = validate_id_for_country("12345678A12", "ZWE") assert result["valid"] is True def test_zimbabwe_invalid(self): result = validate_id_for_country("12345678112", "ZWE") assert result["valid"] is False def test_uganda_valid(self): result = validate_id_for_country("AB123456789012", "UGA") assert result["valid"] is True def test_zambia_valid(self): result = validate_id_for_country("1234567890", "ZMB") assert result["valid"] is True def test_unknown_country_passes(self): result = validate_id_for_country("ANY123", "XXX") assert result["valid"] is True assert result["method"] == "no_rules" def test_empty_id_fails(self): result = validate_id_for_country("", "ZAF") assert result["valid"] is False class TestCrossValidateTransposition: def test_transposed_dob_auto_corrected(self): # OCR extracted date has day/month swapped compared to ID-encoded date fields = { "date_of_birth": "1988-05-03", "sex": "Male", } parsed = { "date_of_birth": "1988-03-05", "sex": "Male", "citizenship": "SA Citizen", "is_valid": True, } result = cross_validate(fields, parsed) assert result["dob_cross_check"] is True assert result["dob_transposed"] is True # The field should be corrected in-place to match the ID assert fields["date_of_birth"] == "1988-03-05"