from pathlib import Path from common_cents.csv_export import write_csv from common_cents.csv_import import parse_csv def _row(date, cents, cat, merch=None, notes=None, tags=None): return { "date": date, "cents": cents, "category": cat, "merchant": merch, "notes": notes, "tags": tags or [], } def test_export_then_import_roundtrip(tmp_path: Path): rows = [ _row("2026-01-01", 100, "A", "M", None, ["x", "y"]), _row("2026-01-02", 200, "B", None, 'Hello, "world"'), _row("2026-01-03", 300, "C:D", None, None, ["t1", "t2", "t3"]), ] p = tmp_path / "out.csv" write_csv(p, rows) result = parse_csv(p) assert result.errors == [] assert len(result.rows) == 3 assert result.rows[0].tags == ["x", "y"] assert result.rows[1].notes == 'Hello, "world"' assert result.rows[2].tags == ["t1", "t2", "t3"] def test_parse_csv_missing_required(tmp_path: Path): p = tmp_path / "bad.csv" p.write_text("DATE,MERCHANT\n2026-01-01,Joe\n") result = parse_csv(p) assert result.errors assert "CATEGORY" in result.errors[0] assert "CENTS" in result.errors[0] def test_parse_csv_missing_optional_warns(tmp_path: Path): p = tmp_path / "ok.csv" p.write_text("DATE,CENTS,CATEGORY,MERCHANT\n2026-01-01,100,Food,Joe\n") result = parse_csv(p) assert result.errors == [] assert any("NOTES" in w and "TAGS" in w for w in result.warnings) assert len(result.rows) == 1 def test_parse_csv_invalid_date(tmp_path: Path): p = tmp_path / "bad.csv" p.write_text("DATE,CENTS,CATEGORY,MERCHANT\nnot-a-date,100,Food,\n") result = parse_csv(p) assert any("invalid date" in e for e in result.errors) assert result.rows == [] def test_parse_csv_zero_cents_rejected(tmp_path: Path): p = tmp_path / "bad.csv" p.write_text("DATE,CENTS,CATEGORY,MERCHANT\n2026-01-01,0,Food,\n") result = parse_csv(p) assert any("positive" in e for e in result.errors) def test_parse_csv_comma_formatted_cents(tmp_path: Path): p = tmp_path / "ok.csv" p.write_text( 'DATE,CENTS,CATEGORY,MERCHANT\n2026-01-01,"14,901",Food,\n' ) result = parse_csv(p) assert result.errors == [] assert result.rows[0].cents == 14901 def test_export_sorts_nothing_within_row(tmp_path: Path): """write_csv preserves caller's tag order; sorting is the DB layer's job.""" p = tmp_path / "out.csv" write_csv(p, [_row("2026-01-01", 100, "A", None, None, ["zeta", "alpha"])]) text = p.read_text() assert '"zeta,alpha"' in text