diff --git a/apps/backend/tests/api/test_health.py b/apps/backend/tests/api/test_health.py index f0d647f..a7a61b6 100644 --- a/apps/backend/tests/api/test_health.py +++ b/apps/backend/tests/api/test_health.py @@ -1,12 +1,9 @@ -from collections.abc import AsyncIterator +from collections.abc import Callable import pytest -from fastapi import FastAPI from httpx import AsyncClient from sqlalchemy.exc import OperationalError -from app.db.session import get_session - async def test_liveness_exposes_service_metadata(client: AsyncClient) -> None: response = await client.get("/api/v1/health/live") @@ -20,15 +17,10 @@ async def test_liveness_exposes_service_metadata(client: AsyncClient) -> None: } -async def test_readiness_reports_the_timescaledb_version(app: FastAPI, client: AsyncClient) -> None: - class ReadySession: - async def scalar(self, *_: object, **__: object) -> str: - return "2.22.1" - - async def override() -> AsyncIterator[ReadySession]: - yield ReadySession() - - app.dependency_overrides[get_session] = override +async def test_readiness_reports_the_timescaledb_version( + fake_session: Callable[..., None], client: AsyncClient +) -> None: + fake_session(result="2.22.1") response = await client.get("/api/v1/health/ready") @@ -41,16 +33,9 @@ async def test_readiness_reports_the_timescaledb_version(app: FastAPI, client: A async def test_readiness_returns_503_when_the_extension_is_missing( - app: FastAPI, client: AsyncClient + fake_session: Callable[..., None], client: AsyncClient ) -> None: - class SessionWithoutExtension: - async def scalar(self, *_: object, **__: object) -> None: - return None - - async def override() -> AsyncIterator[SessionWithoutExtension]: - yield SessionWithoutExtension() - - app.dependency_overrides[get_session] = override + fake_session(result=None) response = await client.get("/api/v1/health/ready") @@ -67,16 +52,9 @@ async def test_readiness_returns_503_when_the_extension_is_missing( ids=["erreur_sqlalchemy", "erreur_reseau_asyncpg"], ) async def test_readiness_returns_503_when_database_is_unreachable( - app: FastAPI, client: AsyncClient, failure: Exception + fake_session: Callable[..., None], client: AsyncClient, failure: Exception ) -> None: - class UnreachableSession: - async def scalar(self, *_: object, **__: object) -> None: - raise failure - - async def override() -> AsyncIterator[UnreachableSession]: - yield UnreachableSession() - - app.dependency_overrides[get_session] = override + fake_session(failure=failure) response = await client.get("/api/v1/health/ready") diff --git a/apps/backend/tests/conftest.py b/apps/backend/tests/conftest.py index e855f1b..950fc69 100644 --- a/apps/backend/tests/conftest.py +++ b/apps/backend/tests/conftest.py @@ -1,13 +1,14 @@ import os -from collections.abc import AsyncIterator, Iterator +from collections.abc import AsyncIterator, Callable, Iterator import pytest from fastapi import FastAPI from httpx import ASGITransport, AsyncClient from app.core.config import get_settings -from app.db.session import get_engine, get_session_factory +from app.db.session import get_engine, get_session, get_session_factory from app.main import create_app +from tests.factories import FakeSession # Piege : les variables d'environnement priment sur apps/backend/.env. Celles qu'on ne @@ -52,3 +53,14 @@ async def client(app: FastAPI) -> AsyncIterator[AsyncClient]: transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as async_client: yield async_client + + +@pytest.fixture +def fake_session(app: FastAPI) -> Callable[..., None]: + def install(result: object = None, failure: Exception | None = None) -> None: + async def override() -> AsyncIterator[FakeSession]: + yield FakeSession(result=result, failure=failure) + + app.dependency_overrides[get_session] = override + + return install diff --git a/apps/backend/tests/factories.py b/apps/backend/tests/factories.py new file mode 100644 index 0000000..05ba3fc --- /dev/null +++ b/apps/backend/tests/factories.py @@ -0,0 +1,37 @@ +from typing import Any + +from app.core.config import Settings + +SETTINGS_DE_TEST: dict[str, Any] = { + "env": "local", + "debug": False, + "log_level": "WARNING", + "cors_origins": "", + "secret_key": "secret-de-test", + "database_url": "postgresql+asyncpg://enervision:change_me@localhost:5433/enervision_test", +} + + +class FakeSession: + """Session factice : renvoie `result`, ou leve `failure` si elle est fournie.""" + + def __init__(self, result: object = None, failure: Exception | None = None) -> None: + self._result = result + self._failure = failure + + async def scalar(self, *_: object, **__: object) -> object: + return self._repondre() + + async def execute(self, *_: object, **__: object) -> object: + return self._repondre() + + def _repondre(self) -> object: + if self._failure is not None: + raise self._failure + return self._result + + +# Piege : les arguments nommes priment sur l'environnement et sur .env, contrairement +# aux variables posees par la fixture `environment`, qui restent surchargeables. +def make_settings(**overrides: Any) -> Settings: + return Settings(**{**SETTINGS_DE_TEST, **overrides})