"""Pin the Money serialization contract (Phase 2 Decimal migration, BE-02): NUMERIC storage, but JSON emits plain numbers so the frontend's `number` types keep working.""" import json from datetime import datetime from decimal import Decimal from sqlmodel import select from app.models.models import ( Account, AccountRead, Bank, Currency, Transaction, TransactionRead, ) def test_money_columns_are_numeric(): assert str(Transaction.__table__.c.amount.type) == "NUMERIC(15, 2)" assert str(Account.__table__.c.balance.type) == "NUMERIC(15, 2)" def test_money_serializes_as_json_number(): read = TransactionRead( id=1, amount=Decimal("25000.50"), merchant="X", date=datetime(2026, 4, 1), created_at=datetime(2026, 4, 1), ) dumped = read.model_dump(mode="json") assert dumped["amount"] == 25000.5 assert isinstance(dumped["amount"], float) # and the whole payload is json.dumps-able (no Decimal leaks) json.dumps(dumped) def test_money_accepts_float_input(): # n8n and the SPA POST plain JSON numbers; they must coerce to Decimal. read = AccountRead( id=1, bank=Bank.BAC, currency=Currency.CRC, label="x", balance=1234.56, updated_at=datetime(2026, 4, 1), ) assert read.balance == Decimal("1234.56") def test_decimal_roundtrip_through_db(session): session.add( Transaction( amount=Decimal("9999.99"), merchant="ROUNDTRIP", date=datetime(2026, 4, 1), ) ) session.commit() row = session.exec( select(Transaction).where(Transaction.merchant == "ROUNDTRIP") ).one() assert isinstance(row.amount, Decimal) assert row.amount == Decimal("9999.99")