"""CSV Amazon transaction reader — Belgium/FR June sample shape.""" from __future__ import annotations from pathlib import Path import pytest from app.core.csv_reader import CsvReader, parse_amount from app.core.readers import make_reader def test_parse_amount_european(): assert parse_amount("13,49") == 13.49 assert parse_amount("1.234,56") == 1234.56 assert parse_amount("1,234.56") == 1234.56 assert parse_amount("-6,24") == -6.24 assert parse_amount("0") == 0.0 assert parse_amount("") == 0.0 def test_parse_amount_unicode_minus_and_spaces(): """Sweden writes negatives with U+2212 and groups thousands with (narrow) NBSP. float() rejects both, and the parser's fallback returned 0.0 — every negative kronor amount (fees, taxes, transfers) of the Jan-2026 file silently vanished while the positives kept adding up (control C2 caught the +165,810.53 drift).""" assert parse_amount("−35,70") == -35.70 # −35,70 assert parse_amount("−78 690,40") == -78690.40 # −78 690,40 (NBSP) assert parse_amount("−1 234,56") == -1234.56 # narrow NBSP thousands assert parse_amount("1 234,56") == 1234.56 assert parse_amount("–6,24") == -6.24 # en dash used as minus def test_second_amount_column_is_summed_not_dropped(tmp_path: Path): """Australia carries BOTH 'sales tax collected' and 'low value goods' (LVIG GST); the row `total` includes both. Dropping the second column failed control C2 by its sum. Amount-field collisions are summed; only non-amount collisions stay errors.""" body = ( '"preamble"\n' '"date/time","settlement ID","type","order ID","sales tax collected",' '"low value goods","total"\n' '"1 Jan 2026 00:00:00 UTC","123","Order","o-1","10,00","-6,60","3,40"\n' ) p = tmp_path / "2026JanMonthlyTransaction.csv" p.write_text(body, encoding="utf-8-sig") reader = CsvReader(str(p)) mapping = reader.detect() assert mapping.sum_cols == {"sales_tax_collected": [("F", "low value goods")]} assert not mapping.duplicate_fields assert "F" not in mapping.unmapped rows = list(reader.iter_records()) assert len(rows) == 1 assert rows[0]["sales_tax_collected"] == pytest.approx(3.40) # 10.00 + (-6.60) assert rows[0]["total"] == 3.40 assert reader.file_meta.summed_fields == mapping.sum_cols assert not reader.file_meta.unmapped_amount_sums reader.close() def test_make_reader_routes_csv(tmp_path: Path): p = tmp_path / "sample.csv" p.write_text( "preamble\n" "date/time,settlement id,type,total\n" "1 Jun 2026 00:00:00 UTC,123,Order,10.00\n", encoding="utf-8", ) r = make_reader(str(p)) assert type(r).__name__ == "CsvReader" def test_csv_reader_belgium_headers(tmp_path: Path): # Minimal FR/BE Custom Unified Transaction CSV (preamble + header + 1 row). body = ( '"Comprend les transactions Amazon Marketplace"\n' '"Tous les montants sont en EUR, sauf indication contraire"\n' '"date/heure","Identifiant du paiement","type","Numéro de la commande","SKU",' '"description","quantité","site de vente","expédition","ville de la commande",' '"état de la commande","commande postale","ventes de produits",' '"crédits d’expédition","crédits d’emballage-cadeau","Total des réductions",' '"taxe de ventes prélevée","Taxe Marketplace Facilitator","frais de vente",' '"Frais pour le service Expédié par Amazon","autres frais de transaction",' '"autres","total","Statut de la transaction","Date de délivrance de la transaction"\n' '"31 mai 2026 22:00:44 UTC","27177484042","Commande","405-9354558-3629905",' '"SKU1","desc","2","amazon.com.be","Amazon","Enines","","1350","29,74","0","0","0",' '"6,24","-6,24","-4,68","-11,57","0","0","13,49","Effectuée","8 juin 2026 15:26:40 UTC"\n' ) p = tmp_path / "2026JunMonthlyTransaction.csv" p.write_text(body, encoding="utf-8-sig") reader = CsvReader(str(p)) mapping = reader.detect() assert not mapping.missing_required assert reader.sheet_name == "CSV" assert reader.file_meta.currency == "EUR" rows = list(reader.iter_records()) assert len(rows) == 1 rec = rows[0] assert rec["settlement_id"] == "27177484042" assert rec["txn_type"] == "Commande" assert rec["marketplace"] == "amazon.com.be" assert rec["total"] == 13.49 assert rec["product_sales"] == 29.74 assert rec["_date"].isoformat() == "2026-05-31" assert reader.file_meta.imported_rows == 1 reader.close()