Files
espresso_frame/server/tests/test_migrations.py
T
tfaour 31adc34a19
Build and push server image / test (push) Successful in 1m18s
Build and push server image / build-and-push (push) Successful in 2m12s
Add a real pytest suite, gating the CI build
64 tests covering: auth/setup and the CSRF gate, the "owner adds their
own data, anyone linked can mute it" permission pattern shared across
calendar-select/tasks-source/whiteboard-source, migration correctness
(fresh install, idempotent re-run, expected columns), battery estimate
outlier rejection, calendar_feed's fetch/merge/partial-failure handling,
webdav_client's fetch/list-directory, the whiteboard force-refresh
throttle bypass and browse endpoint, and render-size invariants across
calendar views/orientations.

No DB/HTTP fixtures need Docker, Node, or a real Immich/CalDAV/WebDAV
server -- a fresh temp SQLite file plus a couple of small local HTTP
servers as test doubles cover it all. Table data is wiped and reseeded
between tests rather than relying on SQLAlchemy's transaction-rollback
isolation pattern, which needs a pysqlite event-listener workaround
app/db.py's engine doesn't have and has no reason to gain just for tests.

Wired into .gitea/workflows/server-docker-build.yml as its own job that
build-and-push now depends on, so a failing suite blocks the image push
rather than just running alongside it for show.
2026-07-23 18:51:54 -04:00

60 lines
2.3 KiB
Python

"""migration.py structural sanity: MIGRATIONS is well-formed, a fresh
install lands on the latest schema version with frame #1 + server
settings seeded, and re-running run_migrations() is a true no-op (it's
called unconditionally at every app.main import -- see main.py -- so it
has to tolerate being invoked against an already-current database)."""
from __future__ import annotations
from sqlalchemy import inspect, text
from app import db as db_module
from app.migration import MIGRATIONS, run_migrations
from app.models import Frame, ServerSettings
def test_migrations_list_is_sequential_and_unique():
versions = [v for v, _ in MIGRATIONS]
assert versions == sorted(versions)
assert len(versions) == len(set(versions))
assert versions == list(range(1, len(versions) + 1))
def test_fresh_install_lands_on_latest_version(db_session):
with db_module.engine.connect() as conn:
row = conn.execute(text("SELECT version FROM schema_version")).fetchone()
assert row is not None
assert row[0] == MIGRATIONS[-1][0]
def test_fresh_install_seeds_frame_one_and_server_settings(db_session):
frame = db_session.get(Frame, 1)
assert frame is not None
assert frame.name
settings = db_session.get(ServerSettings, 1)
assert settings is not None
def test_rerunning_migrations_is_a_no_op(db_session):
frame_count_before = len(db_session.query(Frame).all())
run_migrations()
run_migrations()
frame_count_after = len(db_session.query(Frame).all())
assert frame_count_before == frame_count_after == 1
def test_expected_columns_exist_on_current_schema():
"""A light spot-check, not exhaustive -- one column from a handful of
the more recent migrations, to catch an ALTER that silently didn't
apply (e.g. a typo'd table/column name in a migration function)."""
inspector = inspect(db_module.engine)
user_columns = {c["name"] for c in inspector.get_columns("users")}
frame_columns = {c["name"] for c in inspector.get_columns("frames")}
assert "webdav_base_url" in user_columns # migration 15
assert "webdav_username" in user_columns # migration 14
assert "calendar_caldav_url" in user_columns
assert "whiteboard_cached_image" in frame_columns # migration 14
assert "calendar_week_start_offset" in frame_columns