diff --git a/backend/alembic/versions/9f3a2d7c5e11_add_app_settings.py b/backend/alembic/versions/9f3a2d7c5e11_add_app_settings.py index 7a3926f..bc7c681 100644 --- a/backend/alembic/versions/9f3a2d7c5e11_add_app_settings.py +++ b/backend/alembic/versions/9f3a2d7c5e11_add_app_settings.py @@ -25,10 +25,15 @@ def upgrade() -> None: sa.Column('currency', sa.String(length=3), nullable=False, server_default='USD'), sa.PrimaryKeyConstraint('id'), ) - # Singleton row (id=1) — app boot's seed_default_app_settings() also - # get-or-creates it, but seeding it here means it exists immediately - # after migrating, even before the app has started once. - op.execute("INSERT INTO app_settings (id, currency) VALUES (1, 'USD')") + # Singleton row (id=1) — deliberately NOT seeded here anymore (a prior + # version of this migration hardcoded `INSERT ... VALUES (1, 'USD')`, + # which silently ignored AURUM_DEFAULT_CURRENCY on a fresh install: the + # row already existed by the time app boot's seed_default_app_settings() + # ran its own get-or-create check, so a non-USD default never took). + # seed_default_app_settings() runs on every app boot (see main.py's + # lifespan) and already reads get_settings().default_currency correctly + # — leaving row creation to it entirely is both simpler and correct for + # every configured currency, not just USD. def downgrade() -> None: diff --git a/backend/app/db/seed.py b/backend/app/db/seed.py index 20729cd..2662a89 100644 --- a/backend/app/db/seed.py +++ b/backend/app/db/seed.py @@ -79,9 +79,15 @@ async def seed_default_account(session: AsyncSession) -> None: async def seed_default_app_settings(session: AsyncSession) -> None: - """Ensures the singleton app_settings row (id=1) exists — the migration - already inserts it, but this makes app boot self-healing if that row is - ever missing (e.g. a DB restored from a pre-currency-setting backup).""" + """Ensures the singleton app_settings row (id=1) exists, seeded with + AURUM_DEFAULT_CURRENCY. The sole place that row gets created — the + 9f3a2d7c5e11 migration only creates the table now, not the row itself + (an earlier version hardcoded the row to 'USD' at migration time, which + silently ignored AURUM_DEFAULT_CURRENCY on a fresh install since this + function's own get-or-create check would find the row already there). + Runs on every app boot (see main.py's lifespan), so it's also + self-healing if the row is ever missing (e.g. a DB restored from a + pre-currency-setting backup).""" existing = await session.get(AppSettings, 1) if existing is not None: return diff --git a/backend/tests/test_app_settings_seed.py b/backend/tests/test_app_settings_seed.py new file mode 100644 index 0000000..12cb35a --- /dev/null +++ b/backend/tests/test_app_settings_seed.py @@ -0,0 +1,45 @@ +"""Regression coverage for seed_default_app_settings() respecting +AURUM_DEFAULT_CURRENCY. An earlier version of the 9f3a2d7c5e11 migration +hardcoded the singleton app_settings row to currency='USD' at migration +time — since this function only creates the row when it's missing, that +pre-existing row silently defeated a non-USD AURUM_DEFAULT_CURRENCY on a +fresh install. The migration no longer inserts the row at all; this +function is now the sole place it gets created. +""" +from sqlalchemy import delete + +from app.core.config import get_settings +from app.db.seed import seed_default_app_settings +from app.models.settings import AppSettings + + +async def test_seed_creates_the_row_with_the_configured_currency(test_sessionmaker, monkeypatch): + monkeypatch.setattr(get_settings(), "default_currency", "GBP") + + async with test_sessionmaker() as session: + await session.execute(delete(AppSettings)) + await session.commit() + + await seed_default_app_settings(session) + + settings_row = await session.get(AppSettings, 1) + assert settings_row is not None + assert settings_row.currency == "GBP" + + +async def test_seed_never_overwrites_an_existing_row(test_sessionmaker, monkeypatch): + """Self-healing only kicks in when the row is missing — an existing + row's currency (e.g. one the user changed via Settings after install) + must never be silently reset just because the app restarted.""" + async with test_sessionmaker() as session: + existing = await session.get(AppSettings, 1) + existing.currency = "EUR" + await session.commit() + + monkeypatch.setattr(get_settings(), "default_currency", "GBP") + + async with test_sessionmaker() as session: + await seed_default_app_settings(session) + + settings_row = await session.get(AppSettings, 1) + assert settings_row.currency == "EUR"