Testing Raptor (backend_v2)
Two-dialect test strategy
Raptor's test suite supports two database dialects:
| Mode | Dialect | When | How to invoke |
|---|---|---|---|
| SQLite (local dev) | sqlite |
Local dev — fast, no Postgres required | pytest backend_v2/tests |
| Postgres | postgresql |
CI gate + local validation | DATABASE_URL=... pytest backend_v2/tests |
Both modes share the same test code. The db_engine and db_session fixtures
in backend_v2/conftest.py detect the dialect from DATABASE_URL and adapt
automatically. Individual tests do not need any dialect-specific code.
CI note: As of 2026-07-03 the SQLite-mode
backend-testsCI job has been retired. The singlebackend-tests-postgresjob (display name "Backend tests") is now the canonical CI signal. SQLite mode is still valid for local dev.
ADR reference: docs/architecture/adr/0070-pytest-postgresql-over-testcontainers.md
SQLite mode (default)
No environment variables needed. Run from the repo root:
python -m pytest backend_v2/tests -q
An ephemeral SQLite file is created under pytest's tmp_path per session.
The Alembic baseline migration (0001_raptor_baseline.py) is applied
automatically so the schema matches the Postgres-typed DDL. The DB is
discarded after the session.
Postgres mode (CI gate)
Prerequisites
macOS (developer machine):
brew install postgresql@15
brew services start postgresql@15
# The installer adds pg_ctl to PATH automatically; verify:
pg_ctl --version
Create the test database:
createdb raptor_test
Ubuntu / Debian (CI or Linux dev machine):
sudo apt-get update && sudo apt-get install -y postgresql postgresql-client
sudo -u postgres createuser --superuser "$USER"
createdb raptor_test
Run against Postgres
DATABASE_URL=postgresql://localhost/raptor_test \
python -m pytest backend_v2/tests -q
Or use the --postgres pytest flag (spawns an ephemeral pg_ctl process via
pytest-postgresql; does not require a running Postgres service):
python -m pytest backend_v2/tests -q --postgres
The --postgres flag is useful for quick one-off validation on a machine that
has pg_ctl on PATH but no persistent Postgres instance running. In CI the
services: postgres: container approach is used instead (lower latency).
When to run Postgres mode locally
- Before opening a PR that touches any SQL, a migration, or a callsite that
uses raw
text()queries. Dialect-specific bugs (e.g.%svs:paramparameter style,JSONBvsJSON) only surface on the Postgres path. - After porting a callsite from raw SQLite SQL to SQLAlchemy (RM-4 through RM-7).
- When debugging a CI failure on the
Backend tests (Postgres)job.
Fixtures
db_engine (session-scoped)
A SQLAlchemy Engine connected to the test database. The Alembic baseline
migration is applied once at session startup. Use this when you need direct
engine access (reflection, DDL inspection, bulk operations).
def test_table_exists(db_engine):
from sqlalchemy import inspect
assert "symbols" in inspect(db_engine).get_table_names()
db_session (function-scoped)
A SQLAlchemy Session that rolls back after each test. Use this for all
DML — rows inserted in one test are never visible in the next.
def test_insert_visible_in_session(db_session):
from sqlalchemy import text
db_session.execute(text("INSERT INTO symbols (symbol, ...) VALUES (...)"))
db_session.flush()
result = db_session.execute(text("SELECT symbol FROM symbols")).fetchall()
assert len(result) == 1
Legacy fixtures
Tests that predate RM-8 (test_engine.py, test_baseline_migration.py) use
their own in-module fixtures and do not depend on db_engine / db_session.
This is intentional — the RM-8 fixtures are opt-in for new tests.
CI jobs
| Job id | Display name | Dialect | Trigger |
|---|---|---|---|
backend-tests-postgres |
Backend tests | Postgres 15 | Every push to develop where backend == true or deps == true |
The backend-tests (SQLite) job was retired 2026-07-03 (#1556, #2125).
The Postgres job is the sole backend CI signal and also runs the coverage gate
(floor 30%, tracked to 40% in #2125).
The job uses a services: postgres:15 container and sets
DATABASE_URL=postgresql://raptor:raptor_test@localhost:5432/raptor_test.
alembic upgrade head is run as a pre-test step so the schema is applied
before pytest collects tests. A 90-minute job cap and --timeout=120 per-test
limit mitigate the known pytest-cov hang (#3928).
pyproject.toml / pytest.ini
Pytest is configured via pyproject.toml at the repo root. The --postgres
flag is registered in backend_v2/conftest.py via pytest_addoption.
Common failures
| Symptom | Likely cause | Fix |
|---|---|---|
alembic upgrade head fails in conftest |
0001_raptor_baseline.py has a syntax error for the active dialect |
Run alembic upgrade head manually with DATABASE_URL set and inspect the error |
pytest_postgresql not installed |
requirements.txt not installed in current venv |
pip install -r backend_v2/requirements.txt |
pg_ctl: command not found when using --postgres |
Postgres not installed locally | brew install postgresql@15 (macOS) or apt-get install postgresql |
CI Backend tests fails but local SQLite passes |
Dialect-specific SQL (e.g. ? placeholder, SQLite-only pragma) |
Replace with SQLAlchemy parameterised form; run locally with DATABASE_URL=... pytest |