Raptor's test suite runs against two database dialects:
| Mode | Dialect | When | How to invoke |
|---|---|---|---|
| SQLite (default) | sqlite |
Local dev — fast, no Postgres required | pytest backend_v2/tests |
| Postgres | postgresql |
CI gate + optional 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.
ADR reference: docs/architecture/adr/0070-pytest-postgresql-over-testcontainers.md
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.
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
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).
text() queries. Dialect-specific bugs (e.g. %s vs :param
parameter style, JSONB vs JSON) only surface on the Postgres path.Backend tests (Postgres) job.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
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.
| Job | Dialect | Trigger |
|---|---|---|
backend-tests |
SQLite | Every PR + push to main where code == true |
backend-tests-postgres |
Postgres 15 | Every PR + push to main where code == true |
Both jobs are non-optional. A PR cannot merge if either job fails.
The Postgres CI 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.
Pytest is configured via pyproject.toml at the repo root. The --postgres
flag is registered in backend_v2/conftest.py via pytest_addoption.
| 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 |
| Postgres CI job fails but SQLite passes | Dialect-specific SQL (e.g. ? placeholder, SQLite-only pragma) |
Replace with SQLAlchemy parameterised form |