init
This commit is contained in:
commit
ac93e6074b
38 changed files with 7162 additions and 0 deletions
82
tests/test_csv.py
Normal file
82
tests/test_csv.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue