Testing the Event Store Schema¶
How CALISTA tests the event-store schema and migrations across SQLite and PostgreSQL.
What’s covered¶
- Types (unit-ish)
- PortableJSON round-trip (SQLite JSON / Postgres JSONB)
- UTCDateTime timezone/nullable behavior
- Schema shape (integration)
- Columns + nullability
- PK / UQ:
pk_event_store,uq_event_store_event_id,uq_event_store_stream_id_version - CHECKs:
version >= 1,length(event_id) = 26 - Server default for
recorded_at - Append-only invariants (integration)
UPDATEis rejectedDELETEis rejected (Enforced by triggers: PL/pgSQL on Postgres;RAISE(ABORT)on SQLite when DB is created via Alembic)- Migrations smoke
alembic upgrade headcreatesevent_storealembic downgrade basedrops it- Verified on SQLite (file DB) and Postgres (Testcontainers)
- Postgres extras
- JSONB GIN indexes on
payload,metadata(PG-only)
Fixtures (tests/conftest.py)¶
sqlite_engine_memory— In-memory SQLite; tables viametadata.create_all()(no Alembic, thus no triggers).sqlite_engine_file— File-backed SQLite; migrated via Alembic to head (triggers available).pg_url— Session-scoped Postgres 17 (Testcontainers); migrated to head once.postgres_engine— Per-test engine usingpg_url; truncatesevent_storeafter each test.engine— Indirection to parametrize tests over the above engines.make_event()— Factory that returns a validevent_storerow dict.
Test modules¶
-
tests/integration/test_event_store_schema.pyInspects table/columns and asserts nullability, checks, PK/UQ names, server defaults, and type/dialect expectations. -
tests/integration/test_event_store_append_only.pyVerifies append-only behavior (parametrized overpostgres_engineandsqlite_engine_file): -
Seed a row, assert
UPDATE/DELETEraise, and the row remains unchanged/present. -
tests/integration/test_migrations_roundtrip_sqlite.pyFile-backed SQLite: -
upgrade head⇒event_storeexists (viasqlite_master) -
downgrade base⇒event_storeabsent -
tests/integration/test_migrations_roundtrip_pg.pyPostgres 17 via Testcontainers: - Creates a scratch DB (AUTOCOMMIT on
postgres) upgrade head⇒event_storeexists; typed insert via SAevent_storetabledowngrade base⇒event_storeabsent- Drops the scratch DB
Which backend for which test?¶
| Area | SQLite (memory) | SQLite (file, Alembic) | Postgres 17 |
|---|---|---|---|
| Types / simple DDL checks | ✅ | ✅ | ✅ |
| Triggers (append-only) | ❌ (no Alembic) | ✅ | ✅ |
| JSONB + GIN | ❌ | ❌ | ✅ |
| Upgrade/Downgrade smoke | ❌ | ✅ | ✅ |
Running¶
All tests:
poetry run pytest
Only migrations smoke:
poetry run pytest tests/integration/test_migrations_roundtrip_pg.py
poetry run pytest tests/integration/test_migrations_roundtrip_sqlite.py
Only append-only:
poetry run pytest tests/integration/test_event_store_append_only.py
Single test:
poetry run pytest tests/integration/test_event_store_append_only.py::test_update_is_blocked
Notes / gotchas¶
- Triggers exist only when the DB is created via Alembic. The in-memory SQLite fixture uses
create_all()and therefore does not have triggers. - Postgres JSONB GIN indexes are PG-only and are guarded in the migration; SQLite will not have them.
- Round-trip tests rely on
env.pyresolving the URL via-x url=.../config/env so migrations run against the ephemeral databases created by the tests.