Phase 1 (rename DB) commit 1a: shim every search_space_id /
owner_search_space_id column to the renamed physical column
(workspace_id / owner_workspace_id) via Column("workspace_id", ...),
keeping the ORM attribute name unchanged so existing callers are
untouched. Flip all Table-level references that resolve by column key
(inner __table_args__ strings, the ck_connections_scope_owner CHECK
text, and the _INDEX_DEFINITIONS runtime DDL) plus the searchspace-named
constraint/index names to workspace_id. Update the three raw-SQL test
fixtures that referenced the physical column.
Note: SQLAlchemy defaults a column's key to its name, so passing an
explicit "workspace_id" name moves the Table.c key to workspace_id;
Table-level string refs must therefore change in this phase (the
opposite of the original plan's finding 1). The ORM attribute, the
SearchSpace class, relationships, table names, and FK target strings are
intentionally left for later commits/Phase 2.
Verified: unit 2375 passed/1 skip, integration 346 passed (baseline
parity); create_all builds every table with workspace_id.
|
||
|---|---|---|
| .. | ||
| e2e | ||
| fixtures | ||
| integration | ||
| unit | ||
| utils | ||
| __init__.py | ||
| conftest.py | ||
| README.md | ||
Tests
How the backend test suite is organized and the conventions to follow when adding tests.
Layout: type-first, module-mirrored
Tests are split by type at the top level, and each type mirrors the app/ module tree inside:
tests/
├── conftest.py # global fixtures + DATABASE_URL pinning
├── unit/ # pure logic: no DB, no app, no network
│ └── notifications/
│ ├── api/test_transform.py
│ └── service/
│ ├── messages/test_connector_indexing.py
│ └── test_metadata.py
└── integration/ # real PostgreSQL (pgvector)
├── conftest.py # async engine, transactional db_session, db_user, ...
└── notifications/
├── conftest.py # module-scoped fixtures (e.g. transactional client)
└── test_*_handler.py
To find a feature's tests, look under tests/<type>/<same path as app/>.
Unit vs integration
@pytest.mark.unit— pure, fast, no I/O. Test behavior through a public function's inputs/outputs.@pytest.mark.integration— requires a real database. Run withAUTH_TYPE=LOCAL.
Maximize logic covered by unit tests; keep integration tests for what genuinely needs the DB (persistence, SQL filters, scoping, HTTP wiring).
Principles
- Behavior, not implementation. Assert observable outputs (returned values, persisted rows, HTTP responses), never private helpers. Tests should survive a refactor.
- Functional core / imperative shell. Put pure decision logic in a side-effect-free module (e.g.
app/notifications/service/messages/) so it is unit-testable; keep the persistence shell thin and cover it with a few integration tests. - One responsibility per test file, mirroring the slice it covers.
- Mock only at system boundaries (external APIs, brokers), never internal collaborators. Prefer dependency overrides and the transactional
db_sessionover mocks.
Fixtures
conftest.py is scoped to its directory and below. Keep truly global fixtures in tests/conftest.py; put module-specific fixtures in that module's conftest.py so a DB fixture never loads for a pure unit test.
For API integration tests, override get_async_session and get_auth_context to ride the test's transactional db_session (see tests/integration/notifications/conftest.py): rows seeded in the test and rows read via the endpoint share one transaction that rolls back automatically.
Import mode
The suite uses --import-mode=importlib with pythonpath = ["."] (see pyproject.toml). This lets test files share basenames across modules (e.g. many test_api.py) without __init__.py boilerplate; new test directories do not need an __init__.py.
Running
# fast unit tests
uv run pytest -m unit
# integration (needs Postgres + pgvector)
AUTH_TYPE=LOCAL uv run pytest -m integration
# a single module's tests
uv run pytest tests/unit/notifications