|
| 1 | +"""Stable-string contract tests (core spec §7.4): every enum member and |
| 2 | +stable tag has a canonical triggering input, parametrized by iterating |
| 3 | +the registries -- a new member without an entry here fails loudly.""" |
| 4 | +import pytest |
| 5 | + |
| 6 | +from nameparser import Parser, Policy, parse |
| 7 | +from nameparser._policy import PatronymicRule |
| 8 | +from nameparser._types import STABLE_TAGS, AmbiguityKind |
| 9 | + |
| 10 | +_AMBIGUITY_TRIGGERS: dict[AmbiguityKind, str | None] = { |
| 11 | + AmbiguityKind.PARTICLE_OR_GIVEN: "Van Johnson", |
| 12 | + AmbiguityKind.UNBALANCED_DELIMITER: 'Jon "Nick Smith', |
| 13 | + AmbiguityKind.COMMA_STRUCTURE: "Smith, John, Extra, Jr.", |
| 14 | + # no emitter yet -- arrives with locale-pack order detection (2.x) |
| 15 | + AmbiguityKind.ORDER: None, |
| 16 | + # no emitter yet -- arrives with suffix/nickname refinement (2.x) |
| 17 | + AmbiguityKind.SUFFIX_OR_NICKNAME: None, |
| 18 | +} |
| 19 | + |
| 20 | + |
| 21 | +@pytest.mark.parametrize("kind", [ |
| 22 | + pytest.param(k, marks=pytest.mark.xfail( |
| 23 | + strict=True, reason=f"{k.value}: emitter not yet implemented")) |
| 24 | + if k in _AMBIGUITY_TRIGGERS and _AMBIGUITY_TRIGGERS[k] is None else k |
| 25 | + for k in AmbiguityKind |
| 26 | +]) |
| 27 | +def test_every_ambiguity_kind_has_a_registered_trigger( |
| 28 | + kind: AmbiguityKind) -> None: |
| 29 | + assert kind in _AMBIGUITY_TRIGGERS, ( |
| 30 | + f"new AmbiguityKind {kind.value!r} needs a canonical trigger " |
| 31 | + f"(or an explicit None with its planned emitter)") |
| 32 | + trigger = _AMBIGUITY_TRIGGERS[kind] |
| 33 | + assert trigger is not None # None triggers are strict-xfail marked |
| 34 | + kinds = {a.kind for a in parse(trigger).ambiguities} |
| 35 | + assert kind in kinds |
| 36 | + |
| 37 | + |
| 38 | +_PATRONYMIC_TRIGGERS: dict[PatronymicRule, tuple[str, str]] = { |
| 39 | + # rule -> (input, expected given) |
| 40 | + PatronymicRule.EAST_SLAVIC: ("Сидоров Иван Петрович", "Иван"), |
| 41 | + PatronymicRule.TURKIC: ("Mammadova Aygun Ali kizi", "Aygun"), |
| 42 | +} |
| 43 | + |
| 44 | + |
| 45 | +@pytest.mark.parametrize("rule", list(PatronymicRule)) |
| 46 | +def test_every_patronymic_rule_has_a_trigger(rule: PatronymicRule) -> None: |
| 47 | + assert rule in _PATRONYMIC_TRIGGERS |
| 48 | + text, expected_given = _PATRONYMIC_TRIGGERS[rule] |
| 49 | + p = Parser(policy=Policy(patronymic_rules=frozenset({rule}))) |
| 50 | + assert p.parse(text).given == expected_given |
| 51 | + |
| 52 | + |
| 53 | +_TAG_TRIGGERS: dict[str, tuple[str, str]] = { |
| 54 | + # tag -> (input, token text carrying the tag) |
| 55 | + "particle": ("Juan de la Vega", "de"), |
| 56 | + "conjunction": ("Mr. and Mrs. John Smith", "and"), |
| 57 | + "initial": ("John A. Smith", "A."), |
| 58 | +} |
| 59 | + |
| 60 | + |
| 61 | +@pytest.mark.parametrize("tag", sorted(STABLE_TAGS)) |
| 62 | +def test_every_stable_tag_has_a_trigger(tag: str) -> None: |
| 63 | + assert tag in _TAG_TRIGGERS |
| 64 | + text, token_text = _TAG_TRIGGERS[tag] |
| 65 | + pn = parse(text) |
| 66 | + tagged = next(t for t in pn.tokens if t.text == token_text) |
| 67 | + assert tag in tagged.tags |
0 commit comments