103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
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"
|