import json from pathlib import Path import pytest from lead_scoring.cli.openapi import DEFAULT_OPENAPI_PATH, generate_openapi_json, run_openapi_cli def test_generate_openapi_json_is_deterministic(): json_str_1 = generate_openapi_json() json_str_2 = generate_openapi_json() assert json_str_1 == json_str_2 data = json.loads(json_str_1) assert data["info"]["title"] == "Lead Scoring Service" assert "/health" in data["paths"] assert "/v1/scoring-runs" in data["paths"] assert "/v1/scoring-runs/{run_id}" in data["paths"] assert "CreateScoringRunRequest" in data["components"]["schemas"] assert "CreateScoringRunResponse" in data["components"]["schemas"] assert "ScoringRunResponse" in data["components"]["schemas"] assert "ErrorResponse" in data["components"]["schemas"] # Security scheme assertions assert "securitySchemes" in data["components"] assert "HTTPBearer" in data["components"]["securitySchemes"] bearer_scheme = data["components"]["securitySchemes"]["HTTPBearer"] assert bearer_scheme["type"] == "http" assert bearer_scheme["scheme"] == "bearer" # Operation security assertions post_security = data["paths"]["/v1/scoring-runs"]["post"].get("security", []) assert any("HTTPBearer" in s for s in post_security) get_security = data["paths"]["/v1/scoring-runs/{run_id}"]["get"].get("security", []) assert any("HTTPBearer" in s for s in get_security) def test_default_openapi_path_is_at_project_root(): assert DEFAULT_OPENAPI_PATH.name == "openapi.json" assert (DEFAULT_OPENAPI_PATH.parent / "pyproject.toml").exists() def test_openapi_generation_ignores_env_file_pollution(monkeypatch: pytest.MonkeyPatch): # Setting an env var should not mutate deterministic contract generation monkeypatch.setenv("APP_NAME", "mutated-name") json_str_1 = generate_openapi_json() json_str_2 = generate_openapi_json() assert json_str_1 == json_str_2 data = json.loads(json_str_1) assert data["info"]["title"] == "Lead Scoring Service" def test_openapi_cli_generates_file_at_custom_path(tmp_path: Path): out_file = tmp_path / "custom_openapi.json" exit_code = run_openapi_cli(["--output", str(out_file)]) assert exit_code == 0 assert out_file.exists() content = out_file.read_text(encoding="utf-8") assert content == generate_openapi_json() def test_openapi_cli_check_success_when_matching(tmp_path: Path): out_file = tmp_path / "openapi.json" out_file.write_text(generate_openapi_json(), encoding="utf-8") exit_code = run_openapi_cli(["--output", str(out_file), "--check"]) assert exit_code == 0 def test_openapi_cli_check_fails_when_file_absent(tmp_path: Path): out_file = tmp_path / "non_existent_openapi.json" exit_code = run_openapi_cli(["--output", str(out_file), "--check"]) assert exit_code != 0 def test_openapi_cli_check_fails_when_file_stale(tmp_path: Path): out_file = tmp_path / "openapi.json" out_file.write_text('{"stale": true}\n', encoding="utf-8") exit_code = run_openapi_cli(["--output", str(out_file), "--check"]) assert exit_code != 0 def test_generate_openapi_preserves_custom_app_openapi(): from lead_scoring.main import create_app custom_app = create_app() original_openapi = custom_app.openapi def _custom_openapi(): schema = original_openapi() schema["info"]["x-custom-extension"] = "pilot-test" return schema custom_app.openapi = _custom_openapi # type: ignore json_str = generate_openapi_json(custom_app) data = json.loads(json_str) assert data["info"].get("x-custom-extension") == "pilot-test"