Harden runtime compatibility policy checks
Some checks failed
Tests / Build and Push CICD Base Image (pull_request) Successful in 1h37m28s
Tests / Build and Push CICD Complete Image (pull_request) Failing after 1h57m45s
Tests / Trailing Whitespace Check (pull_request) Has been skipped
Tests / End of File Check (pull_request) Has been skipped
Tests / YAML Syntax Check (pull_request) Has been skipped
Tests / TOML Syntax Check (pull_request) Has been skipped
Tests / Mixed Line Ending Check (pull_request) Has been skipped
Tests / TOML Formatting Check (pull_request) Has been skipped
Tests / Ruff Linting (pull_request) Has been skipped
Tests / Ruff Format Check (pull_request) Has been skipped
Tests / Pyright Type Check (pull_request) Has been skipped
Tests / Darglint Docstring Check (pull_request) Has been skipped
Tests / No Docstring Types Check (pull_request) Has been skipped
Tests / ESLint Check (pull_request) Has been skipped
Tests / Prettier Format Check (pull_request) Has been skipped
Tests / TypeScript Type Check (pull_request) Has been skipped
Tests / TSDoc Lint Check (pull_request) Has been skipped
Tests / Backend Tests (pull_request) Has been skipped
Tests / Frontend Tests (pull_request) Has been skipped
Tests / Backend Doctests (pull_request) Has been skipped
Tests / Integration Tests (pull_request) Has been skipped
Tests / End-to-End Tests (pull_request) Has been skipped

This commit is contained in:
2026-05-16 09:53:12 -04:00
parent e0804534c6
commit e1e7c45c06
2 changed files with 65 additions and 9 deletions

View File

@@ -15,12 +15,22 @@ REQUIRED_PACKAGE_PINS: dict[str, str] = {
} }
def _parse_required_python(required_python: str) -> tuple[int, int] | None:
"""Parse required Python version from strict major.minor format."""
value = required_python.strip()
pieces = value.split(".")
if len(pieces) != 2 or not all(piece.isdigit() for piece in pieces):
return None
return int(pieces[0]), int(pieces[1])
def _runtime_meets_required_python(required_python: str) -> bool: def _runtime_meets_required_python(required_python: str) -> bool:
"""Return True when the interpreter version satisfies required major.minor.""" """Return True when the interpreter major.minor exactly matches required policy."""
major_str, minor_str = required_python.split(".", maxsplit=1) required = _parse_required_python(required_python)
required = (int(major_str), int(minor_str)) if required is None:
return False
current = (sys.version_info.major, sys.version_info.minor) current = (sys.version_info.major, sys.version_info.minor)
return current >= required return current == required
def _installed_version(package_name: str) -> str: def _installed_version(package_name: str) -> str:
@@ -32,22 +42,32 @@ def compatibility_status() -> dict[str, object]:
"""Compute runtime and pinned-dependency compatibility status.""" """Compute runtime and pinned-dependency compatibility status."""
required_python = os.getenv("BACKEND_REQUIRED_PYTHON", "3.14") required_python = os.getenv("BACKEND_REQUIRED_PYTHON", "3.14")
current_python = f"{sys.version_info.major}.{sys.version_info.minor}" current_python = f"{sys.version_info.major}.{sys.version_info.minor}"
required_python_parsed = _parse_required_python(required_python)
package_checks: dict[str, bool] = {} package_checks: dict[str, bool] = {}
package_errors: dict[str, str] = {}
for package_name, required_version in REQUIRED_PACKAGE_PINS.items(): for package_name, required_version in REQUIRED_PACKAGE_PINS.items():
package_checks[package_name] = ( try:
_installed_version(package_name) == required_version package_checks[package_name] = (
) _installed_version(package_name) == required_version
)
except metadata.PackageNotFoundError as exc:
package_checks[package_name] = False
package_errors[package_name] = str(exc)
runtime_ok = _runtime_meets_required_python(required_python) runtime_ok = required_python_parsed is not None and _runtime_meets_required_python(
required_python
)
packages_ok = all(package_checks.values()) packages_ok = all(package_checks.values())
return { return {
"ok": runtime_ok and packages_ok, "ok": runtime_ok and packages_ok,
"required_python": required_python, "required_python": required_python,
"current_python": current_python, "current_python": current_python,
"python_policy_valid": required_python_parsed is not None,
"required_packages": REQUIRED_PACKAGE_PINS, "required_packages": REQUIRED_PACKAGE_PINS,
"package_checks": package_checks, "package_checks": package_checks,
"package_errors": package_errors,
} }

View File

@@ -1,9 +1,11 @@
"""Integration tests for API endpoints.""" """Integration tests for API endpoints."""
from importlib import metadata
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from backend.main import app from backend.main import app, compatibility_status
client = TestClient(app) client = TestClient(app)
@@ -49,8 +51,42 @@ class TestAPIIntegration:
assert payload["ok"] is True assert payload["ok"] is True
assert payload["required_python"] == "3.14" assert payload["required_python"] == "3.14"
assert "current_python" in payload assert "current_python" in payload
assert payload["python_policy_valid"] is True
assert "required_packages" in payload assert "required_packages" in payload
assert payload["required_packages"]["fastapi"] == "0.120.2" assert payload["required_packages"]["fastapi"] == "0.120.2"
assert payload["required_packages"]["uvicorn"] == "0.38.0" assert payload["required_packages"]["uvicorn"] == "0.38.0"
assert payload["package_errors"] == {}
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False) monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)
def test_startup_rejects_malformed_required_python(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Startup should fail fast when Python policy is not strict major.minor."""
monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14.1")
with pytest.raises(RuntimeError), TestClient(app):
pass
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)
def test_compatibility_status_handles_missing_package_metadata(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Compatibility checks should handle missing package metadata gracefully."""
def raise_not_found(_: str) -> str:
raise metadata.PackageNotFoundError("fake")
monkeypatch.setattr("backend.main._installed_version", raise_not_found)
status = compatibility_status()
package_checks = status["package_checks"]
package_errors = status["package_errors"]
assert status["ok"] is False
assert isinstance(package_checks, dict)
assert isinstance(package_errors, dict)
assert package_checks["fastapi"] is False
assert package_checks["uvicorn"] is False
assert "fastapi" in package_errors
assert "uvicorn" in package_errors