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)