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:
"""Return True when the interpreter version satisfies required major.minor."""
major_str, minor_str = required_python.split(".", maxsplit=1)
required = (int(major_str), int(minor_str))
"""Return True when the interpreter major.minor exactly matches required policy."""
required = _parse_required_python(required_python)
if required is None:
return False
current = (sys.version_info.major, sys.version_info.minor)
return current >= required
return current == required
def _installed_version(package_name: str) -> str:
@@ -32,22 +42,32 @@ def compatibility_status() -> dict[str, object]:
"""Compute runtime and pinned-dependency compatibility status."""
required_python = os.getenv("BACKEND_REQUIRED_PYTHON", "3.14")
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_errors: dict[str, str] = {}
for package_name, required_version in REQUIRED_PACKAGE_PINS.items():
package_checks[package_name] = (
_installed_version(package_name) == required_version
)
try:
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())
return {
"ok": runtime_ok and packages_ok,
"required_python": required_python,
"current_python": current_python,
"python_policy_valid": required_python_parsed is not None,
"required_packages": REQUIRED_PACKAGE_PINS,
"package_checks": package_checks,
"package_errors": package_errors,
}

View File

@@ -1,9 +1,11 @@
"""Integration tests for API endpoints."""
from importlib import metadata
import pytest
from fastapi.testclient import TestClient
from backend.main import app
from backend.main import app, compatibility_status
client = TestClient(app)
@@ -49,8 +51,42 @@ class TestAPIIntegration:
assert payload["ok"] is True
assert payload["required_python"] == "3.14"
assert "current_python" in payload
assert payload["python_policy_valid"] is True
assert "required_packages" in payload
assert payload["required_packages"]["fastapi"] == "0.120.2"
assert payload["required_packages"]["uvicorn"] == "0.38.0"
assert payload["package_errors"] == {}
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