From 2efd67c0141e06b279deed1f815c02bb5d8ac159 Mon Sep 17 00:00:00 2001 From: JT Wei Date: Sun, 5 Apr 2026 17:10:03 +0800 Subject: [PATCH 1/3] fix(test): stabilize asyncpg loop scope and ensure schema in CI --- pyproject.toml | 1 + tests/conftest.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ccb5df3..3c687b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ select = ["E", "F", "I", "N", "W", "UP"] testpaths = ["tests"] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" +asyncio_default_test_loop_scope = "session" markers = [ "integration: tests that require real infra/services", ] diff --git a/tests/conftest.py b/tests/conftest.py index a31aef0..b7b1b1b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,8 @@ from httpx import ASGITransport, AsyncClient from src.api.main import app +from src.models.database import Base +from src.models.db import engine @pytest.fixture(scope="session") @@ -19,6 +21,14 @@ def event_loop(): loop.close() +@pytest.fixture(scope="session", autouse=True) +async def ensure_db_schema(): + """Ensure required tables exist even when app lifespan is not triggered.""" + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + yield + + @pytest.fixture async def client(): """Async test client for the FastAPI app.""" From 63d64ce6b90e16cdf859e3d2c30eb653fb1f5df0 Mon Sep 17 00:00:00 2001 From: JT Wei Date: Sun, 5 Apr 2026 17:22:49 +0800 Subject: [PATCH 2/3] fix(ci): bootstrap database schema before test execution --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2483878..8b3d0e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,22 @@ jobs: print("infra-ready") PY + - name: Prepare test database schema + run: | + python - <<'PY' + import asyncio + from src.models.database import Base + from src.models.db import engine + + async def main() -> None: + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + await engine.dispose() + + asyncio.run(main()) + print("schema-ready") + PY + - name: Run tests run: pytest -q -m "not integration" @@ -136,6 +152,22 @@ jobs: print("infra-ready") PY + - name: Prepare test database schema + run: | + python - <<'PY' + import asyncio + from src.models.database import Base + from src.models.db import engine + + async def main() -> None: + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + await engine.dispose() + + asyncio.run(main()) + print("schema-ready") + PY + - name: Run compare blackbox integration test run: pytest -q -m integration tests/integration/test_compare_blackbox.py From f3bd13557f6ed9e4cc8fed4476637c0a2a0a032b Mon Sep 17 00:00:00 2001 From: JT Wei Date: Sun, 5 Apr 2026 17:57:37 +0800 Subject: [PATCH 3/3] fix(test): run FastAPI lifespan in ASGI test client setup --- tests/conftest.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b7b1b1b..ffb6a66 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,6 +32,7 @@ async def ensure_db_schema(): @pytest.fixture async def client(): """Async test client for the FastAPI app.""" - transport = ASGITransport(app=app) - async with AsyncClient(transport=transport, base_url="http://test") as ac: - yield ac + async with app.router.lifespan_context(app): + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + yield ac