from subprocess import run from textwrap import dedent from pathlib import Path from typer.testing import CliRunner from job_research.cli import app from job_research.storage import load_yaml def test_installed_cli_help_exposes_build_profile_subcommand() -> None: result = run(["uv", "run", "job-research", "--help"], capture_output=True, text=True, check=False) assert result.returncode == 0 assert "build-profile" in result.stdout assert "Build candidate-profile.yaml from CV and markdown profile." in result.stdout def test_installed_cli_subcommand_help_works() -> None: result = run(["uv", "run", "job-research", "build-profile", "--help"], capture_output=True, text=True, check=False) assert result.returncode == 0 assert "Usage: job-research build-profile" in result.stdout def test_build_profile_writes_yaml_from_cv_and_profile(tmp_path) -> None: cv = tmp_path / "cv.txt" cv.write_text( dedent( """ Tonio Example Location: France Languages: French, English Skills: Python, SQL, Terraform Years of experience: 3 Data Engineer at Acme Education: Master of Science at Example University """ ).strip(), encoding="utf-8", ) profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer focused on Python and GCP. ## Target Roles - Data Engineer ## Strengths - Python - SQL ## Skills To Emphasize - GCP - BigQuery ## Constraints - CDI only - France only ## Notes - Slight preference for French listings. - Years of experience feels closer to 4. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" result = run( ["uv", "run", "job-research", "build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)], capture_output=True, text=True, check=False, ) assert result.returncode == 0 assert f"candidate profile written to {out}" in result.stdout assert "Warnings included: 1" in result.stdout payload = load_yaml(out) assert payload["name"] == "Tonio Example" assert payload["summary"] == "Junior data engineer focused on Python and GCP." assert payload["target_roles"] == ["Data Engineer"] assert payload["skills"] == ["Python", "SQL", "Terraform", "GCP", "BigQuery"] assert any(item["field"] == "years_of_experience" for item in payload["warnings"]) def test_build_profile_reports_when_no_warnings_are_included(tmp_path) -> None: cv = tmp_path / "cv.txt" cv.write_text( dedent( """ Tonio Example Location: France Languages: French, English Skills: Python, SQL Data Engineer at Acme Education: Master of Science at Example University """ ).strip(), encoding="utf-8", ) profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer focused on Python and GCP. ## Target Roles - Data Engineer ## Strengths - Python - SQL ## Skills To Emphasize - GCP - BigQuery ## Constraints - CDI only - France only ## Notes - Slight preference for French listings. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" result = run( ["uv", "run", "job-research", "build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)], capture_output=True, text=True, check=False, ) assert result.returncode == 0 assert f"candidate profile written to {out}" in result.stdout assert "No warnings included." in result.stdout def test_build_profile_reports_output_write_failures_cleanly(tmp_path, monkeypatch) -> None: cv = tmp_path / "cv.txt" cv.write_text( dedent( """ Tonio Example Location: France Languages: French, English Skills: Python, SQL Data Engineer at Acme Education: Master of Science at Example University """ ).strip(), encoding="utf-8", ) profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer focused on Python and GCP. ## Target Roles - Data Engineer ## Strengths - Python - SQL ## Skills To Emphasize - GCP - BigQuery ## Constraints - CDI only - France only ## Notes - Slight preference for French listings. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" original_write_text = Path.write_text def flaky_write_text( self: Path, data: str, encoding: str | None = None, errors: str | None = None, newline: str | None = None, ) -> int: if self == out: raise OSError("disk full") return original_write_text(self, data, encoding=encoding, errors=errors, newline=newline) monkeypatch.setattr(Path, "write_text", flaky_write_text) result = CliRunner().invoke(app, ["build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)]) assert result.exit_code == 1 assert not out.exists() assert "Unable to write candidate profile to" in result.stderr assert "disk full" in result.stderr assert "Traceback" not in result.stderr def test_build_profile_rejects_empty_cv_text_before_writing(tmp_path) -> None: cv = tmp_path / "cv.txt" cv.write_text(" \n", encoding="utf-8") profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer. ## Target Roles - Data Engineer ## Strengths - Python ## Skills To Emphasize - BigQuery ## Constraints - CDI only ## Notes - Slight preference for French listings. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" result = run( ["uv", "run", "job-research", "build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)], capture_output=True, text=True, check=False, ) assert result.returncode != 0 assert not out.exists() assert "No readable text found in CV input" in result.stderr assert "Traceback" not in result.stderr def test_build_profile_reports_unreadable_pdf_input_cleanly(tmp_path, monkeypatch) -> None: cv = tmp_path / "cv.pdf" cv.write_bytes(b"%PDF-1.4\n") profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer. ## Target Roles - Data Engineer ## Strengths - Python ## Skills To Emphasize - BigQuery ## Constraints - CDI only ## Notes - Slight preference for French listings. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" def broken_extract_pdf_text(path): raise ValueError("broken pdf") monkeypatch.setattr("job_research.cli.extract_pdf_text", broken_extract_pdf_text) result = CliRunner().invoke(app, ["build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)]) assert result.exit_code == 1 assert not out.exists() assert "CV input not readable" in result.stderr assert "broken pdf" in result.stderr assert "Traceback" not in result.stderr def test_build_profile_reports_malformed_profile_markdown_cleanly(tmp_path) -> None: cv = tmp_path / "cv.txt" cv.write_text( dedent( """ Tonio Example Location: France Languages: French, English Skills: Python """ ).strip(), encoding="utf-8", ) profile = tmp_path / "profile.md" profile.write_text( dedent( """ # Candidate Profile ## Summary Junior data engineer. ## Target Roles Data Engineer ## Strengths - Python ## Skills To Emphasize - BigQuery ## Constraints - CDI only ## Notes - Slight preference for French listings. """ ).strip(), encoding="utf-8", ) out = tmp_path / "candidate-profile.yaml" result = CliRunner().invoke(app, ["build-profile", "--cv", str(cv), "--profile", str(profile), "--out", str(out)]) assert result.exit_code == 1 assert not out.exists() assert "profile markdown invalid" in result.stderr assert "Traceback" not in result.stderr