"""End-to-end tests for the example image-search CLI and its request document.""" from __future__ import annotations import json import subprocess import sys from pathlib import Path import pytest from tools import envelope, example REPO_ROOT = Path(__file__).resolve().parents[1] EXAMPLE_REQUEST = REPO_ROOT / "examples" / "input.json" def run_cli(*args: str) -> subprocess.CompletedProcess[str]: """Run the CLI in a subprocess so stdout/exit codes are asserted end to end.""" return subprocess.run( [sys.executable, "-m", "tools.example", *args], cwd=REPO_ROOT, capture_output=True, text=True, check=False, ) def single_document(stdout: str) -> dict: lines = [line for line in stdout.splitlines() if line.strip()] assert len(lines) == 1, stdout document = json.loads(lines[0]) assert isinstance(document, dict) return document def test_example_request_document_is_canonical_dry_run_request() -> None: document = json.loads(EXAMPLE_REQUEST.read_text(encoding="utf-8")) assert isinstance(document, dict) assert set(example.REQUEST_FIELDS) <= set(document) assert document["dry_run"] is True assert document["format"] in example.ACCEPTED_FORMATS assert isinstance(document["topic"], str) and document["topic"].strip() assert document["source"] in example.default_registry().available_sources() def test_example_request_document_passes_normalization() -> None: document = json.loads(EXAMPLE_REQUEST.read_text(encoding="utf-8")) request = example.normalize_request(document) assert request["dry_run"] is True assert request["format"] == "png" assert request["source"] == "wikimedia_commons" assert request["count"] == document["count"] def test_cli_prints_single_json_envelope_and_exits_zero() -> None: result = run_cli("examples/input.json") assert result.returncode == envelope.EXIT_OK, result.stderr assert result.stderr == "" response = single_document(result.stdout) assert response["status"] == envelope.STATUS_DRY_RUN assert response["source"] == "wikimedia_commons" assert response["items"] == [] assert response["request"]["dry_run"] is True assert response["error"] is None def test_cli_envelope_is_deterministic() -> None: first = run_cli("examples/input.json") second = run_cli("examples/input.json") assert first.returncode == second.returncode == envelope.EXIT_OK assert first.stdout == second.stdout def test_cli_help_exits_zero_and_documents_parameters() -> None: result = run_cli("--help") assert result.returncode == 0 assert "REQUEST" in result.stdout def test_cli_reports_missing_request_document(tmp_path: Path) -> None: missing = tmp_path / "missing.json" result = run_cli(str(missing)) assert result.returncode == envelope.EXIT_INVALID_REQUEST response = single_document(result.stdout) assert response["status"] == envelope.STATUS_INVALID_REQUEST assert response["error"]["code"] == "request_document" def test_cli_reports_malformed_json(tmp_path: Path) -> None: malformed = tmp_path / "malformed.json" malformed.write_text("{not json", encoding="utf-8") result = run_cli(str(malformed)) assert result.returncode == envelope.EXIT_INVALID_REQUEST response = single_document(result.stdout) assert response["status"] == envelope.STATUS_INVALID_REQUEST assert response["error"]["code"] == "request_document" def test_cli_reports_non_object_document(tmp_path: Path) -> None: payload = tmp_path / "list.json" payload.write_text("[]", encoding="utf-8") result = run_cli(str(payload)) assert result.returncode == envelope.EXIT_INVALID_REQUEST assert single_document(result.stdout)["status"] == envelope.STATUS_INVALID_REQUEST @pytest.mark.parametrize( "document", [ {}, {"topic": "red fox"}, {"topic": "", "format": "png"}, {"topic": "red fox", "format": "gif"}, {"topic": "red fox", "format": "png", "count": 0}, ], ) def test_normalize_request_rejects_invalid_documents(document: object) -> None: with pytest.raises(example.RequestError): example.normalize_request(document) # type: ignore[arg-type]