from livesub.models.glossary import Glossary, GlossaryEntry def make() -> Glossary: return Glossary( [ GlossaryEntry("headshot", {"ko": "헤드샷"}), GlossaryEntry("head", {"ko": "머리"}), GlossaryEntry("Nexus", {"ko": "넥서스", "ja": "ネクサス"}), ] ) def test_protect_and_restore_roundtrip(): g = make() protected, repl = g.protect("Nice headshot on the Nexus", "ko") assert "headshot" not in protected assert "Nexus" not in protected assert repl == ["헤드샷", "넥서스"] # 번역 모델이 플레이스홀더를 그대로 통과시켰다고 가정 assert Glossary.restore(protected, repl) == "Nice 헤드샷 on the 넥서스" def test_longest_match_wins(): """'headshot'이 'head'에 먼저 잡아먹히면 안 된다.""" g = make() _, repl = g.protect("headshot", "ko") assert repl == ["헤드샷"] def test_word_boundary_for_latin_terms(): g = make() _, repl = g.protect("overheated", "ko") assert repl == [] def test_missing_target_language_is_left_alone(): g = make() protected, repl = g.protect("headshot", "ja") assert protected == "headshot" assert repl == [] def test_prompt_hint_only_includes_present_terms(): g = make() hint = g.prompt_hint("push to the Nexus", "ko") assert "Nexus -> 넥서스" in hint assert "headshot" not in hint assert g.prompt_hint("nothing here", "ko") == "" def test_case_insensitive_by_default(): g = make() _, repl = g.protect("NEXUS down", "ko") assert repl == ["넥서스"] def test_save_and_load(tmp_path): path = tmp_path / "glossary.json" make().save(path) loaded = Glossary.load(path) assert len(loaded) == 3 assert loaded.entries[2].target_for("ja") == "ネクサス" def test_add_replaces_existing_entry(): g = make() g.add(GlossaryEntry("Nexus", {"ko": "본진"})) assert len(g) == 3 _, repl = g.protect("Nexus", "ko") assert repl == ["본진"] def test_restore_ignores_out_of_range_placeholder(): assert Glossary.restore("a ⟦5⟧ b", ["x"]) == "a b"