This guide details the testing architecture, pytest conventions, database integration test setups, and manual verification protocols.
All tests live under src/tests/ and are strictly categorized by type and module path:
- Unit Tests:
src/tests/unit/<module_path>/test_*.py - Integration Tests:
src/tests/integration/<module_path>/test_*.py - Support Helpers:
src/tests/support/contains reusable test constants, paths, and mocks.
- No Issue Buckets: Do not create top-level directories named after issue IDs or tickets. Do not name test modules after issues.
- No Loose Root Tests: Do not place
test_*.pyfiles directly undersrc/tests/unit/orsrc/tests/integration/roots. They must map to a specific module subdirectory. - SPDX Headers: All subdirectories must contain
__init__.pyfiles with SPDX headers so that linter namespaces are recognized. - Target Coverage: Maintain >80% coverage for all new Python files. Verify with:
pytest --cov=app --cov-report=html
Async tests must use the AnyIO runner.
- Annotate async tests with
@pytest.mark.anyio. - Enter
AsyncTestClientwithasync withinside fixtures or tests. If you fail to enter the client context, the Litestar app lifespan is not triggered, leaking database connections and Dishka containers across tests.
@pytest.fixture
async def client(app):
async with AsyncTestClient(app) as c:
yield cTests should mock services rather than building real external integrations:
- For handler tests that inject components, use Dishka DI container overrides or mock services directly.
- Ensure route-level DI parameters are satisfied.
Integration tests run against a real Oracle instance. Pytest fixtures manage the lifecycle:
conftest.pyoverrides application settings with the test DSN.src/tests/integration/conftest.pycloses any lingering SQLSpec connection pool before tests start.- The
driverfixture bootstraps tables, runs truncates, and loads fixtures. - Parallel Worker Safety: Do not truncate shared fixture tables in the middle of tests. Use unique keys (e.g. unique SKUs/SKUs with timestamps) to prevent tests running concurrently from colliding, and run targeted cleanup instead.
- The connection pool is closed after each test run to prevent event-loop bind leaks.
Ensure the database is up and migrated before running tests:
make start-infra
uv run python manage.py database upgrade --no-prompt- Vector Search: Assert specific metadata, score bounds, and result counts. Do not assert the exact text of AI-generated answers, as models are non-deterministic.
- Telemetry: Assert that
search_metricsandsql_phasescontain timing entries. - HTMX Route Verification: Send
HX-Request: trueheader in test clients or use HTMX conftest fixtures. Assert that HTMX actions return the expected partial HTML block (e.g.hx-swaptargets, specific HTML class/id).
For changes affecting the UI or user-facing flow, provide a manual verification plan.
The automated tests have passed. For manual verification, please follow these steps:
**Manual Verification Steps:**
1. Start the development server with: `uv run coffee run`
2. Open browser to: `http://localhost:8000`
3. Confirm that you see: [User action and visual outcome]
The automated tests have passed. For manual verification, please follow these steps:
**Manual Verification Steps:**
1. Ensure the server is running.
2. Execute: `curl -X POST http://localhost:8000/api/chat/stream -d '{"message": "Hello"}'`
3. Confirm that you receive: [Expected HTTP code, headers, or JSON keys]
A phase is only checkpointed after passing automated tests and securing the user's manual validation feedback.