223 lines
6.9 KiB
Python
223 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from resume_harness.cli import main
|
|
from resume_harness.io import InputError, load_mapping
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_validate_examples_without_exposing_contact(capsys: pytest.CaptureFixture[str]) -> None:
|
|
exit_code = main(
|
|
[
|
|
"validate",
|
|
"--candidate",
|
|
str(ROOT / "examples/candidate.sample.yaml"),
|
|
"--job",
|
|
str(ROOT / "examples/job.sample.yaml"),
|
|
"--config",
|
|
str(ROOT / "examples/config.sample.yaml"),
|
|
]
|
|
)
|
|
|
|
captured = capsys.readouterr()
|
|
payload = json.loads(captured.out)
|
|
assert exit_code == 0
|
|
assert payload["status"] == "valid"
|
|
assert payload["evidence_count"] == 8
|
|
assert "haneul.kim@example.com" not in captured.out
|
|
assert "010-1234-5678" not in captured.out
|
|
|
|
|
|
def test_schema_command_emits_json_schema(capsys: pytest.CaptureFixture[str]) -> None:
|
|
assert main(["schema", "candidate"]) == 0
|
|
payload = json.loads(capsys.readouterr().out)
|
|
assert payload["title"] == "CandidateProfile"
|
|
|
|
|
|
def test_render_emits_only_quality_gated_markdown(
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
exit_code = main(
|
|
[
|
|
"render",
|
|
"--candidate",
|
|
str(ROOT / "examples/candidate.sample.yaml"),
|
|
"--job",
|
|
str(ROOT / "examples/job.sample.yaml"),
|
|
"--draft",
|
|
str(ROOT / "examples/draft.sample.yaml"),
|
|
"--config",
|
|
str(ROOT / "examples/config.sample.yaml"),
|
|
"--analysis",
|
|
str(ROOT / "examples/job-analysis.sample.yaml"),
|
|
"--evidence-map",
|
|
str(ROOT / "examples/evidence-map.sample.yaml"),
|
|
"--content-plan",
|
|
str(ROOT / "examples/content-plan.sample.yaml"),
|
|
"--quality-report",
|
|
str(ROOT / "examples/quality-report.sample.yaml"),
|
|
]
|
|
)
|
|
|
|
output = capsys.readouterr().out
|
|
assert exit_code == 0
|
|
assert output.startswith("# 김하늘\n")
|
|
assert "## 경력" in output
|
|
assert "근거 ID" not in output
|
|
|
|
|
|
def test_render_rejects_stale_quality_report_after_draft_changes(
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
draft = load_mapping(ROOT / "examples/draft.sample.yaml")
|
|
draft["sections"][0]["claims"][0]["text"] += " 변경"
|
|
changed_draft = tmp_path / "changed-draft.json"
|
|
changed_draft.write_text(
|
|
json.dumps(
|
|
draft,
|
|
ensure_ascii=False,
|
|
default=lambda value: value.isoformat(),
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
exit_code = main(
|
|
[
|
|
"render",
|
|
"--candidate",
|
|
str(ROOT / "examples/candidate.sample.yaml"),
|
|
"--job",
|
|
str(ROOT / "examples/job.sample.yaml"),
|
|
"--draft",
|
|
str(changed_draft),
|
|
"--config",
|
|
str(ROOT / "examples/config.sample.yaml"),
|
|
"--analysis",
|
|
str(ROOT / "examples/job-analysis.sample.yaml"),
|
|
"--evidence-map",
|
|
str(ROOT / "examples/evidence-map.sample.yaml"),
|
|
"--content-plan",
|
|
str(ROOT / "examples/content-plan.sample.yaml"),
|
|
"--quality-report",
|
|
str(ROOT / "examples/quality-report.sample.yaml"),
|
|
]
|
|
)
|
|
|
|
captured = capsys.readouterr()
|
|
assert exit_code == 2
|
|
assert "현재 초안 내용과 일치하지 않습니다" in captured.err
|
|
assert captured.out == ""
|
|
|
|
|
|
def test_render_rejects_quality_report_when_analysis_context_changes(
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
analysis = load_mapping(ROOT / "examples/job-analysis.sample.yaml")
|
|
analysis["summary"] = "내용은 같아 보여도 다른 평가 컨텍스트"
|
|
changed_analysis = tmp_path / "changed-analysis.json"
|
|
# YAML timestamps are loaded as datetime objects; serialise them exactly as
|
|
# the CLI's JSON input contract expects.
|
|
changed_analysis.write_text(
|
|
json.dumps(
|
|
analysis,
|
|
ensure_ascii=False,
|
|
default=lambda value: value.isoformat(),
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
exit_code = main(
|
|
[
|
|
"render",
|
|
"--candidate",
|
|
str(ROOT / "examples/candidate.sample.yaml"),
|
|
"--job",
|
|
str(ROOT / "examples/job.sample.yaml"),
|
|
"--draft",
|
|
str(ROOT / "examples/draft.sample.yaml"),
|
|
"--config",
|
|
str(ROOT / "examples/config.sample.yaml"),
|
|
"--analysis",
|
|
str(changed_analysis),
|
|
"--evidence-map",
|
|
str(ROOT / "examples/evidence-map.sample.yaml"),
|
|
"--content-plan",
|
|
str(ROOT / "examples/content-plan.sample.yaml"),
|
|
"--quality-report",
|
|
str(ROOT / "examples/quality-report.sample.yaml"),
|
|
]
|
|
)
|
|
|
|
captured = capsys.readouterr()
|
|
assert exit_code == 2
|
|
assert "평가 컨텍스트와 일치하지 않습니다" in captured.err
|
|
assert captured.out == ""
|
|
|
|
|
|
def test_render_rejects_quality_report_when_candidate_context_changes(
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
candidate = load_mapping(ROOT / "examples/candidate.sample.yaml")
|
|
candidate["headline"] = "평가 이후에 변경된 프로필 정보"
|
|
changed_candidate = tmp_path / "changed-candidate.json"
|
|
changed_candidate.write_text(
|
|
json.dumps(
|
|
candidate,
|
|
ensure_ascii=False,
|
|
default=lambda value: value.isoformat(),
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
exit_code = main(
|
|
[
|
|
"render",
|
|
"--candidate",
|
|
str(changed_candidate),
|
|
"--job",
|
|
str(ROOT / "examples/job.sample.yaml"),
|
|
"--draft",
|
|
str(ROOT / "examples/draft.sample.yaml"),
|
|
"--config",
|
|
str(ROOT / "examples/config.sample.yaml"),
|
|
"--analysis",
|
|
str(ROOT / "examples/job-analysis.sample.yaml"),
|
|
"--evidence-map",
|
|
str(ROOT / "examples/evidence-map.sample.yaml"),
|
|
"--content-plan",
|
|
str(ROOT / "examples/content-plan.sample.yaml"),
|
|
"--quality-report",
|
|
str(ROOT / "examples/quality-report.sample.yaml"),
|
|
]
|
|
)
|
|
|
|
captured = capsys.readouterr()
|
|
assert exit_code == 2
|
|
assert "평가 컨텍스트와 일치하지 않습니다" in captured.err
|
|
assert captured.out == ""
|
|
|
|
|
|
def test_load_mapping_rejects_non_mapping(tmp_path: Path) -> None:
|
|
path = tmp_path / "input.yaml"
|
|
path.write_text("- item\n", encoding="utf-8")
|
|
|
|
with pytest.raises(InputError, match="mapping"):
|
|
load_mapping(path)
|
|
|
|
|
|
def test_load_mapping_rejects_unknown_extension(tmp_path: Path) -> None:
|
|
path = tmp_path / "input.txt"
|
|
path.write_text("value: 1\n", encoding="utf-8")
|
|
|
|
with pytest.raises(InputError, match="지원 형식"):
|
|
load_mapping(path)
|