"""Fast unit tests for the engine (synthetic data, no large files).""" from __future__ import annotations from datetime import date from app.core.dates import parse_amazon_date, parse_amazon_datetime from app.core.column_map import build_mapping, normalize_header from app.core.settlements import aggregate, classify from app.core.receivable import compute_receivable, classify_aging # --------------------------------------------------------------------------- dates def test_parse_amazon_dates(): assert parse_amazon_date("Jan 15, 2026 5:45:40 PM PST") == date(2026, 1, 15) assert parse_amazon_date("Jan 30, 2026 9:40:25 PM PST") == date(2026, 1, 30) assert parse_amazon_date("Dec 1, 2025 12:00:00 AM PST") == date(2025, 12, 1) assert parse_amazon_date("2026-01-31 23:59:59") == date(2026, 1, 31) assert parse_amazon_date("garbage") is None dt, tz = parse_amazon_datetime("Jan 15, 2026 5:45:40 PM PST") assert dt.hour == 17 and tz == "PST" # --------------------------------------------------------------------------- mapping def test_column_mapping_normalizes_headers(): assert normalize_header("Order-ID") == "order id" assert normalize_header(" Total ") == "total" header = [("A", "date/time"), ("B", "settlement id"), ("C", "type"), ("I", "account type"), ("AD", "total"), ("Z", "SomeUnknownCol")] m = build_mapping(header, header_row=8) assert m.is_valid assert m.field_to_col["settlement_id"] == "B" assert m.field_to_col["total"] == "AD" assert "Z" in m.unmapped def test_column_mapping_missing_required(): m = build_mapping([("A", "date/time"), ("B", "sku")], header_row=8) assert not m.is_valid assert "settlement_id" in m.missing_required assert "total" in m.missing_required # --------------------------------------------------------------------------- aging def test_aging_bands(): assert classify_aging(None) == "Current" assert classify_aging(0) == "Current" assert classify_aging(15) == "1-30" assert classify_aging(45) == "31-60" assert classify_aging(75) == "61-90" assert classify_aging(120) == "91-Over" # --------------------------------------------------------------------------- settlements def _rec(total, ttype, acct, sid, d, mkt="USA"): return { "total": total, "txn_type": ttype, "account_type": acct, "settlement_id": sid, "_date": d, "_marketplace": mkt, "date_time": d.isoformat() if d else None, } def _scenario(): # Standard: 100 paid (payout tagged 200, Jan 6 received), # 200 receivable (payout tagged 300, Jan 30 in-transit), # 300 receivable (no payout yet). recs = [] recs += [_rec(1000.0, "Order", "Standard Orders", "100", date(2026, 1, 5))] recs += [_rec(2000.0, "Order", "Standard Orders", "200", date(2026, 1, 15))] recs += [_rec(500.0, "Order", "Standard Orders", "300", date(2026, 1, 31))] # transfers (tagged to the next/open settlement) recs += [_rec(-1000.0, "Transfer", "Standard Orders", "200", date(2026, 1, 6))] recs += [_rec(-2000.0, "Transfer", "Standard Orders", "300", date(2026, 1, 30))] # an Invoiced chain, independent recs += [_rec(300.0, "Order", "Invoiced Orders", "150", date(2026, 1, 10))] recs += [_rec(-300.0, "Transfer", "Invoiced Orders", "250", date(2026, 1, 12))] recs += [_rec(80.0, "Order", "Invoiced Orders", "250", date(2026, 1, 20))] return recs def test_classification_and_receivable(): agg = aggregate(_scenario()) cls = classify(agg, month_end=date(2026, 1, 31), clearing_lag_days=2) def status(acct, sid): return agg.settlements[("USA", acct, sid)].status assert status("Standard Orders", "100") == "paid" assert status("Standard Orders", "200") == "receivable" assert status("Standard Orders", "300") == "receivable" assert status("Invoiced Orders", "150") == "paid" assert status("Invoiced Orders", "250") == "receivable" # Jan-30 transfer is in-transit; Jan-6 transfer received. by_sid = {(t.account_type, t.settlement_id): t for t in agg.transfers} assert by_sid[("Standard Orders", "300")].received is False assert by_sid[("Standard Orders", "200")].received is True rec = compute_receivable(agg, cls) usa = rec.marketplaces["USA"] assert usa.accounts["Standard Orders"].additional_sales == 2500.0 # 2000 + 500 assert usa.accounts["Invoiced Orders"].additional_sales == 80.0 assert usa.receivable_local == 2580 # rounded total def test_clearing_lag_override_moves_boundary(): # With lag=0, the Jan-30 transfer counts as received -> settlement 300 becomes paid. agg = aggregate(_scenario()) cls = classify(agg, month_end=date(2026, 1, 31), clearing_lag_days=0) assert agg.settlements[("USA", "Standard Orders", "300")].status == "receivable" # boundary = max received tagged = 300 -> only >=300 receivable; 200 becomes paid assert agg.settlements[("USA", "Standard Orders", "200")].status == "paid" rec = compute_receivable(agg, cls) assert rec.marketplaces["USA"].accounts["Standard Orders"].additional_sales == 500.0