diff --git a/backend/tests/test_basic.py b/backend/tests/test_basic.py index ed83e5a..9eb0a88 100644 --- a/backend/tests/test_basic.py +++ b/backend/tests/test_basic.py @@ -1,6 +1,7 @@ """Basic tests for the backend application.""" from importlib import metadata +from pathlib import Path from typing import Any, cast from unittest.mock import AsyncMock @@ -9,6 +10,7 @@ from fastapi.testclient import TestClient from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession +from backend import main as backend_main from backend.main import ( app, compatibility_check, @@ -18,6 +20,12 @@ from backend.main import ( validate_runtime_policy, ) +parse_exact_pin = backend_main.__dict__["_parse_exact_pin"] +required_package_pins = backend_main.__dict__["_required_package_pins"] +required_package_pins_from_dependencies = backend_main.__dict__[ + "_required_package_pins_from_dependencies" +] + def test_app_creation(): """Test that the FastAPI app is created properly.""" @@ -142,3 +150,131 @@ def test_compatibility_check_returns_status() -> None: """Compatibility endpoint helper should return policy payload.""" payload = compatibility_check() assert "ok" in payload + + +def test_parse_exact_pin_handles_valid_and_invalid_requirements() -> None: + """Exact pin parsing should normalize extras and reject malformed pins.""" + assert parse_exact_pin("psycopg[binary]==3.2.12 ; python_version >= '3.14'") == ( + "psycopg", + "3.2.12", + ) + assert parse_exact_pin("uvicorn>=0.51.0") is None + assert parse_exact_pin("==0.51.0") is None + assert parse_exact_pin("uvicorn==") is None + + +def test_required_package_pins_from_dependencies_filters_non_exact_pins() -> None: + """Only exact dependency pins should be included in runtime policy.""" + dependencies = [ + "fastapi==0.139.0", + "uvicorn>=0.51.0", + "psycopg[binary]==3.2.12", + ] + + assert required_package_pins_from_dependencies(dependencies) == { + "fastapi": "0.139.0", + "psycopg": "3.2.12", + } + + +def test_required_package_pins_uses_installed_metadata( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Installed project metadata should be the primary pin source.""" + + def fake_requires(_: str) -> list[str]: + return ["fastapi==0.139.0", "psycopg[binary]==3.2.12"] + + def unexpected_read_text(_: Path, encoding: str = "utf-8") -> str: + del encoding + raise AssertionError("pyproject fallback should not run") + + monkeypatch.setattr(backend_main.metadata, "requires", fake_requires) + monkeypatch.setattr(Path, "read_text", unexpected_read_text) + + assert required_package_pins() == { + "fastapi": "0.139.0", + "psycopg": "3.2.12", + } + + +def test_required_package_pins_falls_back_to_pyproject( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Pyproject should be used when installed metadata is unavailable or unusable.""" + + def missing_requires(_: str) -> list[str]: + raise metadata.PackageNotFoundError("missing") + + def fake_read_text(_: Path, encoding: str = "utf-8") -> str: + del encoding + return "[project]\ndependencies = []\n" + + def fake_toml_loads(_: str) -> dict[str, object]: + return { + "project": { + "dependencies": [ + "fastapi==0.139.0", + 123, + "psycopg[binary]==3.2.12", + ] + } + } + + monkeypatch.setattr(backend_main.metadata, "requires", missing_requires) + monkeypatch.setattr(Path, "read_text", fake_read_text) + monkeypatch.setattr(backend_main.tomllib, "loads", fake_toml_loads) + + assert required_package_pins() == { + "fastapi": "0.139.0", + "psycopg": "3.2.12", + } + + +def test_required_package_pins_handles_invalid_pyproject_shapes( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Invalid pyproject structures should safely produce no required pins.""" + + def missing_requires(_: str) -> list[str]: + raise metadata.PackageNotFoundError("missing") + + def fake_read_text(_: Path, encoding: str = "utf-8") -> str: + del encoding + return "[project]\ndependencies = []\n" + + def invalid_project_loads(_: str) -> dict[str, object]: + return {"project": []} + + def missing_dependencies_loads(_: str) -> dict[str, object]: + return {"project": {}} + + monkeypatch.setattr(backend_main.metadata, "requires", missing_requires) + monkeypatch.setattr(Path, "read_text", fake_read_text) + + monkeypatch.setattr(backend_main.tomllib, "loads", invalid_project_loads) + assert required_package_pins() == {} + + monkeypatch.setattr(backend_main.tomllib, "loads", missing_dependencies_loads) + assert required_package_pins() == {} + + +def test_compatibility_status_rejects_malformed_required_python( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Malformed Python policy strings should fail compatibility checks.""" + + def empty_required_package_pins() -> dict[str, str]: + return {} + + monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14.1") + monkeypatch.setattr( + backend_main, + "_required_package_pins", + empty_required_package_pins, + ) + + status = cast("dict[str, Any]", compatibility_status()) + + assert status["ok"] is False + assert status["python_policy_valid"] is False