|
2 | 2 | """Unit tests for relation_aliases() parser, surface_variants(), and collision guard.""" |
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import unicodedata |
| 6 | + |
5 | 7 | import pytest |
6 | 8 |
|
7 | 9 | import common |
@@ -77,6 +79,27 @@ def test_line_with_not_two_backtick_groups_skipped(self, tmp_path): |
77 | 79 | )) |
78 | 80 | assert common.relation_aliases(tmp_path) == {"a": "b"} |
79 | 81 |
|
| 82 | + def test_missing_arrow_not_parsed_as_mapping(self, tmp_path): |
| 83 | + # Two backtick groups but NO `->` between them must NOT become a mapping |
| 84 | + # (the arrow is required — two stray backticks are not a declaration). |
| 85 | + _write_alias_file(tmp_path, ( |
| 86 | + "- `a` `b`\n" |
| 87 | + "- `x` -> `y`\n" |
| 88 | + )) |
| 89 | + aliases = common.relation_aliases(tmp_path) |
| 90 | + assert "a" not in aliases |
| 91 | + assert aliases == {"x": "y"} |
| 92 | + |
| 93 | + def test_wrong_arrow_not_parsed_as_mapping(self, tmp_path): |
| 94 | + # A non-`->` separator (e.g. `<-`) must NOT become a mapping. |
| 95 | + _write_alias_file(tmp_path, ( |
| 96 | + "- `a` <- `b`\n" |
| 97 | + "- `x` -> `y`\n" |
| 98 | + )) |
| 99 | + aliases = common.relation_aliases(tmp_path) |
| 100 | + assert "a" not in aliases |
| 101 | + assert aliases == {"x": "y"} |
| 102 | + |
80 | 103 |
|
81 | 104 | # --------------------------------------------------------------------------- |
82 | 105 | # surface_variants |
@@ -138,3 +161,38 @@ def test_duplicate_raw_same_canonical_allowed(self, tmp_path): |
138 | 161 | )) |
139 | 162 | aliases = common.relation_aliases(tmp_path) |
140 | 163 | assert aliases == {"a": "b"} |
| 164 | + |
| 165 | + |
| 166 | +# --------------------------------------------------------------------------- |
| 167 | +# NFC normalization (macOS-authored NFD files must match NFC CSV relations) |
| 168 | +# --------------------------------------------------------------------------- |
| 169 | + |
| 170 | +class TestRelationAliasesNFC: |
| 171 | + """A KB file authored on macOS is often NFD (decomposed) Hangul, but the CSV |
| 172 | + relations cmd_provenance compares against are NFC-normalized. So the parsed |
| 173 | + keys/values MUST be NFC or the canonical lookup and surface match silently |
| 174 | + miss for exactly the Korean predicates this feature targets (#188).""" |
| 175 | + |
| 176 | + def test_nfd_key_normalized_to_nfc(self, tmp_path): |
| 177 | + nfd = unicodedata.normalize("NFD", "게재연도") |
| 178 | + assert nfd != unicodedata.normalize("NFC", "게재연도") # sanity: forms differ |
| 179 | + _write_alias_file(tmp_path, f"- `{nfd}` -> `published_year`\n") |
| 180 | + key = next(iter(common.relation_aliases(tmp_path))) |
| 181 | + assert unicodedata.is_normalized("NFC", key) |
| 182 | + assert key == unicodedata.normalize("NFC", "게재연도") |
| 183 | + |
| 184 | + def test_nfd_canonical_normalized_to_nfc(self, tmp_path): |
| 185 | + nfd = unicodedata.normalize("NFD", "게재연도") |
| 186 | + _write_alias_file(tmp_path, f"- `publication_year` -> `{nfd}`\n") |
| 187 | + canonical = common.relation_aliases(tmp_path)["publication_year"] |
| 188 | + assert unicodedata.is_normalized("NFC", canonical) |
| 189 | + assert canonical == unicodedata.normalize("NFC", "게재연도") |
| 190 | + |
| 191 | + def test_nfc_query_matches_nfd_authored_surface(self, tmp_path): |
| 192 | + # A canonical queried in NFC finds an NFD-authored surface variant. |
| 193 | + nfd = unicodedata.normalize("NFD", "게재연도") |
| 194 | + _write_alias_file(tmp_path, f"- `{nfd}` -> `published_year`\n") |
| 195 | + aliases = common.relation_aliases(tmp_path) |
| 196 | + assert unicodedata.normalize("NFC", "게재연도") in common.surface_variants( |
| 197 | + "published_year", aliases |
| 198 | + ) |
0 commit comments