mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 11:48:46 +02:00
Models use Money = Annotated[Decimal, PlainSerializer(float)] so storage is exact NUMERIC(15,2) (rates 15,6) while JSON keeps emitting plain numbers — the frontend contract is unchanged, pinned by test_models_serialization.py. Service layers coerce to float at their boundaries (projection math, rate multipliers, agent tool output, the savings constants become Decimal) so no naive Decimal/float mixing can raise. Migration 7f567c8bafdb applied to dev: per-row values and the table sum byte-identical before/after; endpoints verified live on the new schema. 59/59 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
1.8 KiB
Python
68 lines
1.8 KiB
Python
"""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")
|