Files
bambuddy/backend/tests/unit/services/test_finance_service_defaults.py
maziggy 4a85e033c0 fix(finance): show the currency the install is configured for (issue #3123)
The Finance page was the only surface in Bambuddy that read its currency
from a data row rather than the `currency` setting, and it fell back to EUR
where every other page falls back to USD. One variable drives every amount
on that page, so the personal balance, the cost-center budgets and the whole
transaction list were wrong together on any install not set to euros. It now
takes the configured currency from /settings/ui-flags, which is readable by
anyone who can see Finance -- /settings needs SETTINGS_READ, which a
cost_centers:read_own user does not have.

The backend was the other half. Of the four places that settle on a
currency, three wrote a hardcoded "EUR": the wallet the API mints on demand,
the wallet a print charge mints when none exists, and the balance returned
for a user with no wallet row at all. All four now go through one resolver,
which lives beside the rest of the balance logic.

The wallet's currency column is removed outright rather than merely ignored.
An install has one currency and nothing here converts between them, so a
per-wallet copy could only ever drift from the setting -- and a column
nothing reads is a trap for whoever finds it next. A startup migration drops
it on both SQLite and PostgreSQL, after the raw CREATE TABLE that would
otherwise re-add it on an install whose finance tables predate the ORM.
SQLite builds older than 3.35 have no DROP COLUMN and keep it, harmlessly,
since it has a default and no reader.

Saving settings now invalidates the ui-flags query too. Nothing did, so a
changed currency sat behind that query's staleTime before showing up. The
sponsor prompt's own EUR fallback is now USD, matching AppSettings.
2026-09-20 10:06:47 +02:00

92 lines
3.1 KiB
Python

"""Unit tests for finance defaults applied during user creation/update."""
import pytest
from sqlalchemy import select
from backend.app.models.finance import CostCenter, CostCenterMember, UserWallet
from backend.app.models.settings import Settings
from backend.app.models.user import User
from backend.app.services.finance_defaults import ensure_user_finance_defaults
class TestFinanceDefaults:
@pytest.mark.asyncio
async def test_creates_wallet_private_center_and_membership(self, db_session):
db_session.add(Settings(key="currency", value="USD"))
user = User(username="alice", role="user", is_active=True)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
changed = await ensure_user_finance_defaults(db_session, user)
await db_session.commit()
assert changed is True
wallet = await db_session.scalar(select(UserWallet).where(UserWallet.user_id == user.id))
assert wallet is not None
assert wallet.balance == 0.0
center = await db_session.scalar(
select(CostCenter).where(CostCenter.owner_user_id == user.id, CostCenter.is_private.is_(True))
)
assert center is not None
assert center.name == "alice"
membership = await db_session.scalar(
select(CostCenterMember).where(
CostCenterMember.cost_center_id == center.id,
CostCenterMember.user_id == user.id,
)
)
assert membership is not None
assert membership.can_print is True
@pytest.mark.asyncio
async def test_updates_private_center_name_and_is_idempotent(self, db_session):
user = User(username="bob", role="user", is_active=True)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
initial_changed = await ensure_user_finance_defaults(db_session, user)
await db_session.commit()
assert initial_changed is True
user.username = "bobby"
renamed_changed = await ensure_user_finance_defaults(db_session, user)
await db_session.commit()
assert renamed_changed is True
center = await db_session.scalar(
select(CostCenter).where(CostCenter.owner_user_id == user.id, CostCenter.is_private.is_(True))
)
assert center is not None
assert center.name == "bobby"
idempotent_changed = await ensure_user_finance_defaults(db_session, user)
assert idempotent_changed is False
@pytest.mark.asyncio
async def test_reactivates_existing_private_center(self, db_session):
user = User(username="carol", role="user", is_active=True)
db_session.add(user)
await db_session.flush()
center = CostCenter(
name=user.username,
is_active=False,
is_private=True,
owner_user_id=user.id,
)
db_session.add(center)
await db_session.commit()
changed = await ensure_user_finance_defaults(db_session, user)
await db_session.commit()
assert changed is True
assert center.is_active is True