Files
resume-haness/tests/test_prompts.py
T

134 lines
4.0 KiB
Python

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")