mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 16:08:47 +02:00
Exchange-rate fetchers log every source failure instead of silently passing (BE-18); the rate tool exposes fetched_at so staleness is visible (BE-20). Agent: unbound-session failures raise a clear RuntimeError (BE-12); net worth converts via get_crc_multipliers with last-known fallbacks instead of a hardcoded 600 CRC / 1.08 EUR guess, and reports accounts it cannot convert rather than inventing numbers (BE-24); budget tool enforces MIN/MAX_YEAR (BE-16); municipal receipts gain offset paging (BE-17); category analytics prefetches names; the module docstring pins the read-only tool policy (SEC-09). Note: the refresh loop never swallowed CancelledError (BaseException since 3.8) — ARCH-08 was a false positive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
152 lines
4.0 KiB
Python
152 lines
4.0 KiB
Python
"""Agent tool coverage: session binding, JSON-safe output (no Decimal leaks),
|
|
and the batched queries introduced in Phase 2."""
|
|
|
|
import json
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from app.agent import tools
|
|
from app.agent.tools import reset_session, set_session
|
|
from app.models.models import (
|
|
Account,
|
|
AccountType,
|
|
Bank,
|
|
Currency,
|
|
MunicipalReceipt,
|
|
PensionSnapshot,
|
|
Transaction,
|
|
WaterMeterReading,
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def bound_session(session):
|
|
token = set_session(session)
|
|
yield session
|
|
reset_session(token)
|
|
|
|
|
|
def make_snapshot(fund: Bank, start: date, end: date, saldo: str) -> PensionSnapshot:
|
|
zero = Decimal("0")
|
|
return PensionSnapshot(
|
|
fund=fund,
|
|
contract_number="C-1",
|
|
period_start=start,
|
|
period_end=end,
|
|
saldo_anterior=zero,
|
|
aportes=zero,
|
|
rendimientos=zero,
|
|
retiros=zero,
|
|
traslados=zero,
|
|
comision=zero,
|
|
correccion=zero,
|
|
bonificacion=zero,
|
|
saldo_final=Decimal(saldo),
|
|
source_filename="t.pdf",
|
|
)
|
|
|
|
|
|
def make_receipt(period: str) -> MunicipalReceipt:
|
|
zero = Decimal("0")
|
|
return MunicipalReceipt(
|
|
receipt_date=date(2026, 4, 1),
|
|
due_date=date(2026, 4, 15),
|
|
period=period,
|
|
account="A1",
|
|
finca="F1",
|
|
holder_name="x",
|
|
holder_cedula="x",
|
|
holder_address="x",
|
|
subtotal=Decimal("100"),
|
|
interests=zero,
|
|
iva=Decimal("13"),
|
|
total=Decimal("113"),
|
|
source_filename="r.pdf",
|
|
)
|
|
|
|
|
|
def test_unbound_session_raises(session):
|
|
with pytest.raises(RuntimeError, match="not bound to agent context"):
|
|
tools.get_accounts()
|
|
|
|
|
|
def test_accounts_output_is_json_safe(bound_session):
|
|
bound_session.add(
|
|
Account(
|
|
bank=Bank.BAC,
|
|
currency=Currency.CRC,
|
|
label="Main",
|
|
balance=Decimal("1234.56"),
|
|
account_type=AccountType.BANK,
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
out = tools.get_accounts()
|
|
json.dumps(out) # would raise on Decimal leakage
|
|
assert out[0]["balance"] == 1234.56
|
|
|
|
|
|
def test_pension_latest_only_per_fund(bound_session):
|
|
bound_session.add(
|
|
make_snapshot(Bank.FCL, date(2026, 1, 1), date(2026, 1, 31), "100")
|
|
)
|
|
bound_session.add(
|
|
make_snapshot(Bank.FCL, date(2026, 2, 1), date(2026, 2, 28), "200")
|
|
)
|
|
bound_session.add(
|
|
make_snapshot(Bank.ROP, date(2026, 1, 1), date(2026, 1, 31), "900")
|
|
)
|
|
bound_session.commit()
|
|
|
|
out = tools.get_pension_snapshots(latest_only=True)
|
|
json.dumps(out)
|
|
assert len(out) == 2
|
|
by_fund = {o["fund"]: o for o in out}
|
|
assert by_fund["FCL"]["saldo_final"] == 200.0 # latest period wins
|
|
assert by_fund["ROP"]["saldo_final"] == 900.0
|
|
|
|
full = tools.get_pension_snapshots(latest_only=False)
|
|
assert len(full) == 3
|
|
|
|
|
|
def test_municipal_receipts_batched_consumption(bound_session):
|
|
r1, r2 = make_receipt("2026-03"), make_receipt("2026-04")
|
|
r2.account = "A2"
|
|
bound_session.add(r1)
|
|
bound_session.add(r2)
|
|
bound_session.commit()
|
|
bound_session.refresh(r1)
|
|
bound_session.refresh(r2)
|
|
for rid, m3 in ((r1.id, "10.5"), (r1.id, "4.5"), (r2.id, "7")):
|
|
bound_session.add(
|
|
WaterMeterReading(
|
|
meter_id="7335",
|
|
period=f"p{m3}",
|
|
consumption_m3=Decimal(m3),
|
|
receipt_id=rid,
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
|
|
out = tools.get_municipal_receipts()
|
|
json.dumps(out)
|
|
by_account = {o["account"]: o for o in out}
|
|
assert by_account["A1"]["water_consumption_m3"] == 15.0
|
|
assert by_account["A2"]["water_consumption_m3"] == 7.0
|
|
|
|
|
|
def test_recent_transactions_json_safe(bound_session):
|
|
bound_session.add(
|
|
Transaction(
|
|
amount=Decimal("42.42"),
|
|
merchant="M",
|
|
date=datetime(2026, 4, 1),
|
|
)
|
|
)
|
|
bound_session.commit()
|
|
out = tools.get_recent_transactions()
|
|
json.dumps(out)
|
|
assert out[0]["amount"] == 42.42
|