"""Prompt rendering + policy_version hash 검증.""" from __future__ import annotations import pytest from policy import prompt_render from policy.prompt_render import ( KNOWN_4B_TASKS, KNOWN_26B_TASKS, policy_version, render_26b, render_4b, ) ALL_4B_TASKS = sorted(KNOWN_4B_TASKS) ALL_26B_TASKS = sorted(KNOWN_26B_TASKS) @pytest.mark.parametrize("task", ALL_4B_TASKS) def test_render_4b_basic(policy, task): rendered = render_4b(task, subject_domain="safety_reference", policy=policy) # placeholder 가 남아있지 않아야 함 (정책 주입된 것들) assert "{forbidden_block}" not in rendered assert "{subject_description}" not in rendered assert "{confidence_threshold}" not in rendered assert "{context_cap}" not in rendered # 실제 금지 섹션 텍스트 포함 assert "4B 절대 금지" in rendered # 사용자 input placeholder 는 남아있어야 함 (이중 중괄호 → 단일로 이스케이프됨) # 단, render 시점 이후 .format() 으로 주입되므로 {filename} 같은 건 나중에 치환 @pytest.mark.parametrize("task", ALL_26B_TASKS) def test_render_26b_basic(policy, task): rendered = render_26b(task, subject_domain="safety_reference", policy=policy) assert "{forbidden_block}" not in rendered assert "{subject_description}" not in rendered assert "4B 절대 금지" in rendered def test_render_4b_rejects_26b_task(policy): with pytest.raises(ValueError): render_4b("p3c_deep_summary", subject_domain="msds", policy=policy) def test_render_26b_rejects_4b_task(policy): with pytest.raises(ValueError): render_26b("p3a_short_summary", subject_domain="msds", policy=policy) def test_render_uses_fallback_for_unknown_domain(policy): """unknown subject 도 fallback_domain.description 이 사용되어 렌더 성공.""" rendered = render_4b("p1_triage", subject_domain="__unknown__", policy=policy) assert policy.fallback_domain.description in rendered def test_render_different_domain_different_forbidden_block(policy): """도메인별로 forbidden 블록 내용이 달라짐.""" msds = render_4b("p3a_short_summary", subject_domain="msds", policy=policy) news = render_4b("p3a_short_summary", subject_domain="news_item", policy=policy) # msds 는 safety_sufficiency_assertion 규칙 포함 assert "safety_sufficiency_assertion" in msds # news_item 은 news_multi_source_synthesis 규칙 포함 assert "news_multi_source_synthesis" in news # ===================================================================== # policy_version hash — deterministic # ===================================================================== @pytest.mark.parametrize("task", ALL_4B_TASKS + ALL_26B_TASKS) def test_policy_version_deterministic(policy_yaml_path, task): v1 = policy_version(task, policy_path=policy_yaml_path) v2 = policy_version(task, policy_path=policy_yaml_path) assert v1 == v2 def test_policy_version_length(policy_yaml_path): v = policy_version("p3a_short_summary", policy_path=policy_yaml_path) assert len(v) == 12 # hex 문자열인지 확인 int(v, 16) # raises ValueError if not hex def test_policy_version_differs_across_tasks(policy_yaml_path): v_a = policy_version("p1_triage", policy_path=policy_yaml_path) v_b = policy_version("p3a_short_summary", policy_path=policy_yaml_path) assert v_a != v_b, "다른 template 은 다른 hash 가 나와야 함" def test_policy_version_changes_when_yaml_changes(tmp_path, policy_yaml_path): """yaml 을 바꾸면 hash 가 변한다.""" original = policy_version("p3a_short_summary", policy_path=policy_yaml_path) # yaml 복사본 수정 modified = tmp_path / "modified.yaml" from pathlib import Path original_text = Path(policy_yaml_path).read_text(encoding="utf-8") # 주석 한 줄 추가 — 구조 유지하면서 bytes 만 변경 modified.write_text(original_text + "\n# test modification\n", encoding="utf-8") prompt_render.clear_cache() changed = policy_version("p3a_short_summary", policy_path=str(modified)) assert original != changed, "yaml 바뀌면 hash 도 바뀌어야 함"