from __future__ import annotations from pathlib import Path import pytest from resume_harness.prompts import ( DuplicatePromptIdError, PromptFormatError, PromptRepository, PromptRepositoryError, ) PROJECT_ROOT = Path(__file__).resolve().parents[1] PACKAGE_PROMPT_ROOT = PROJECT_ROOT / "src" / "resume_harness" / "prompt_templates" DEVELOPMENT_PROMPT_ROOT = PROJECT_ROOT / "prompts" def _write_prompt( root: Path, filename: str, *, prompt_id: str = "draft-resume", version: str = "1.2.3", output_model: str | None = "ResumeDraft", body: str = "검증된 사실만 사용한다.", ) -> Path: output_line = "" if output_model is None else f"output_model: {output_model}\n" path = root / filename path.write_text( "---\n" f"id: {prompt_id}\n" f"version: {version}\n" f"{output_line}" "---\n\n" f"{body}\n", encoding="utf-8", ) return path def test_repository_loads_checked_in_prompts_and_optional_output_model() -> None: repository = PromptRepository() draft = repository.get("draft-resume") base = repository.load("base-system") assert draft.version == "1.0.0" assert draft.output_model == "ResumeDraft" assert "근거" in draft.body assert draft.content == draft.body assert base.output_model is None assert repository.list_ids() == tuple(repository) assert repository.root == PACKAGE_PROMPT_ROOT.resolve() def test_development_prompt_mirror_matches_packaged_templates() -> None: packaged = { path.name: path.read_bytes() for path in PACKAGE_PROMPT_ROOT.glob("*.md") } development = { path.name: path.read_bytes() for path in DEVELOPMENT_PROMPT_ROOT.glob("*.md") } assert packaged assert development == packaged def test_repository_rejects_requested_path_traversal(tmp_path: Path) -> None: _write_prompt(tmp_path, "safe.md") repository = PromptRepository(tmp_path) with pytest.raises(PromptRepositoryError, match="invalid requested prompt id"): repository.get("../safe") with pytest.raises(PromptRepositoryError): repository.load("/etc/passwd") def test_repository_rejects_duplicate_ids_case_insensitively(tmp_path: Path) -> None: _write_prompt(tmp_path, "first.md", prompt_id="Draft-Resume") _write_prompt(tmp_path, "second.md", prompt_id="draft-resume") with pytest.raises(DuplicatePromptIdError, match="duplicate prompt id"): PromptRepository(tmp_path) @pytest.mark.parametrize( "front_matter", [ "id: safe\nid: replaced\nversion: 1.0.0\n", "id: safe\nversion: 1.0.0\noutput_model: !!python/name:os.system\n", "id: [safe]\nversion: 1.0.0\n", ], ) def test_repository_uses_strict_safe_front_matter( tmp_path: Path, front_matter: str ) -> None: (tmp_path / "unsafe.md").write_text( f"---\n{front_matter}---\n본문\n", encoding="utf-8" ) with pytest.raises(PromptFormatError, match="front matter"): PromptRepository(tmp_path) def test_repository_validates_required_metadata_and_body(tmp_path: Path) -> None: (tmp_path / "missing.md").write_text( "---\nid: only-id\n---\n본문\n", encoding="utf-8" ) with pytest.raises(PromptFormatError, match="requires id and version"): PromptRepository(tmp_path) def test_repository_rejects_symlinked_prompt_even_when_target_exists( tmp_path: Path, ) -> None: repository_root = tmp_path / "repository" repository_root.mkdir() outside = _write_prompt(tmp_path, "outside.md", prompt_id="outside") (repository_root / "linked.md").symlink_to(outside) with pytest.raises(PromptRepositoryError, match="symbolic links"): PromptRepository(repository_root) def test_repository_reports_unknown_but_well_formed_id(tmp_path: Path) -> None: _write_prompt(tmp_path, "known.md", prompt_id="known") repository = PromptRepository(tmp_path) with pytest.raises(KeyError, match="unknown prompt id"): repository.get("missing")