ix runtime policy pin mismatch and make dependency audits non-blocking #81

Merged
darkhelm merged 18 commits from fix/backend-runtime-policy-tests into main 2026-07-16 07:40:18 -04:00
4 changed files with 108 additions and 11 deletions
Showing only changes of commit 81dac4651f - Show all commits

View File

@@ -701,6 +701,7 @@ jobs:
name: Frontend Dependency Audit
runs-on: ubuntu-act
timeout-minutes: 15
continue-on-error: true
needs: build_cicd
steps:
- *identify_runner_step
@@ -721,10 +722,10 @@ jobs:
TEST_STATUS=${PIPESTATUS[0]}
if [ "${TEST_STATUS}" -ne 0 ]; then
echo " Frontend dependency audit failed (exit=${TEST_STATUS})"
echo "⚠️ Frontend dependency audit failed (exit=${TEST_STATUS})"
echo "--- Last 200 lines of frontend audit output ---"
tail -n 200 "${LOG_FILE}" || true
exit "${TEST_STATUS}"
echo "Proceeding: frontend audit is informational-only."
fi
- *failure_diagnostics_step
@@ -733,6 +734,7 @@ jobs:
name: Backend Dependency Audit
runs-on: ubuntu-act
timeout-minutes: 15
continue-on-error: true
needs: build_cicd
steps:
- *identify_runner_step
@@ -753,10 +755,10 @@ jobs:
TEST_STATUS=${PIPESTATUS[0]}
if [ "${TEST_STATUS}" -ne 0 ]; then
echo " Backend dependency audit failed (exit=${TEST_STATUS})"
echo "⚠️ Backend dependency audit failed (exit=${TEST_STATUS})"
echo "--- Last 200 lines of backend audit output ---"
tail -n 200 "${LOG_FILE}" || true
exit "${TEST_STATUS}"
echo "Proceeding: backend audit is informational-only."
fi
- *failure_diagnostics_step
@@ -784,11 +786,15 @@ jobs:
echo "frontend-audit=${frontend_audit_status}"
echo "backend-audit=${backend_audit_status}"
if [ "${backend_status}" != "success" ] || [ "${precommit_status}" != "success" ] || [ "${frontend_status}" != "success" ] || [ "${xdoctest_status}" != "success" ] || [ "${frontend_audit_status}" != "success" ] || [ "${backend_audit_status}" != "success" ]; then
if [ "${backend_status}" != "success" ] || [ "${precommit_status}" != "success" ] || [ "${frontend_status}" != "success" ] || [ "${xdoctest_status}" != "success" ]; then
echo "❌ One or more CICD source lanes failed"
exit 1
fi
if [ "${frontend_audit_status}" != "success" ] || [ "${backend_audit_status}" != "success" ]; then
echo "⚠️ Dependency audit lane reported non-success (informational only)"
fi
echo "✅ Source checks complete"
- *failure_diagnostics_step

View File

@@ -21,10 +21,10 @@ from backend.database import (
)
REQUIRED_PACKAGE_PINS: dict[str, str] = {
"fastapi": "0.120.2",
"fastapi": "0.139.0",
"psycopg": "3.2.12",
"sqlalchemy": "2.0.44",
"uvicorn": "0.38.0",
"uvicorn": "0.51.0",
}

View File

@@ -95,8 +95,8 @@ class TestAPIIntegration:
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["required_packages"]["fastapi"] == "0.139.0"
assert payload["required_packages"]["uvicorn"] == "0.51.0"
assert payload["package_errors"] == {}
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)

View File

@@ -1,12 +1,22 @@
"""Basic tests for the backend application."""
from typing import cast
from importlib import metadata
from typing import Any, cast
from unittest.mock import AsyncMock
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from backend.main import app, get_api_session, read_root
from backend.main import (
app,
compatibility_check,
compatibility_status,
get_api_session,
read_root,
validate_runtime_policy,
)
def test_app_creation():
@@ -56,3 +66,84 @@ def test_typeguard_validation():
# This would fail with typeguard active, but we'll just test the happy path
# to ensure coverage of our code without breaking the test
def test_health_check_db_unavailable():
"""Health endpoint should return unavailable when DB probe fails."""
unhealthy_session = cast("AsyncSession", AsyncMock(spec=AsyncSession))
unhealthy_session.execute = AsyncMock(
side_effect=SQLAlchemyError("database unavailable")
)
async def override_get_session():
"""Provide an unhealthy session dependency override for tests."""
yield unhealthy_session
app.dependency_overrides[get_api_session] = override_get_session
try:
with TestClient(app) as client:
response = client.get("/health")
assert response.status_code == 503
assert response.json() == {
"status": "unhealthy",
"database": "disconnected",
}
finally:
app.dependency_overrides.clear()
def test_compatibility_status_success(monkeypatch: pytest.MonkeyPatch):
"""Compatibility status should be healthy when all checks match policy."""
monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14")
def installed_version(package_name: str) -> str:
versions = {
"fastapi": "0.139.0",
"psycopg": "3.2.12",
"sqlalchemy": "2.0.44",
"uvicorn": "0.51.0",
}
return versions[package_name]
monkeypatch.setattr("backend.main._installed_version", installed_version)
status = cast("dict[str, Any]", compatibility_status())
assert status["ok"] is True
assert status["package_errors"] == {}
def test_compatibility_status_missing_package(monkeypatch: pytest.MonkeyPatch):
"""Compatibility status should record metadata lookup failures."""
def missing_version(_: str) -> str:
raise metadata.PackageNotFoundError("missing")
monkeypatch.setattr("backend.main._installed_version", missing_version)
status = cast("dict[str, Any]", compatibility_status())
package_checks = cast("dict[str, bool]", status["package_checks"])
package_errors = cast("dict[str, str]", status["package_errors"])
assert status["ok"] is False
assert package_checks["fastapi"] is False
assert "fastapi" in package_errors
def test_validate_runtime_policy_raises(monkeypatch: pytest.MonkeyPatch):
"""Runtime policy validation should fail when compatibility is not ok."""
def invalid_status() -> dict[str, object]:
return {"ok": False}
monkeypatch.setattr("backend.main.compatibility_status", invalid_status)
with pytest.raises(RuntimeError):
validate_runtime_policy()
def test_compatibility_check_returns_status() -> None:
"""Compatibility endpoint helper should return policy payload."""
payload = compatibility_check()
assert "ok" in payload