package parser import ( "os" "path/filepath" "testing" "git.petrovv.com/nikola/money/internal/config" ) func writeFile(t *testing.T, name, content string) string { t.Helper() path := filepath.Join(t.TempDir(), name) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } return path } func TestCSVSignedAmountColumn(t *testing.T) { path := writeFile(t, "st.csv", `Date,Ref,Description,Amount 02.01.2026,X1,LIDL SOFIA 1234,"-45,20" 03.01.2026,X2,ACME PAYROLL,"1.500,00" 04.01.2026,X3,COFFEE,"-3,50" `) acc := &config.Account{Slug: "checking", Currency: "EUR", Parser: "csv", CSV: &config.CSVConfig{ SkipRows: 1, Date: config.Column{Col: 0, Layout: "02.01.2006"}, Description: config.Column{Col: 2}, Amount: &config.Column{Col: 3, Decimal: ",", Thousands: "."}, }} p, err := For(acc) if err != nil { t.Fatal(err) } got, err := p.Parse(path, acc) if err != nil { t.Fatal(err) } want := []RawTxn{ {Date: "2026-01-02", Description: "LIDL SOFIA 1234", AmountMinor: -4520}, {Date: "2026-01-03", Description: "ACME PAYROLL", AmountMinor: 150000}, {Date: "2026-01-04", Description: "COFFEE", AmountMinor: -350}, } if len(got) != len(want) { t.Fatalf("got %d txns, want %d: %+v", len(got), len(want), got) } for i := range want { if got[i] != want[i] { t.Errorf("txn %d = %+v, want %+v", i, got[i], want[i]) } } } func TestCSVDebitCreditPair(t *testing.T) { path := writeFile(t, "st.csv", `2026-02-01;RENT;800.00; 2026-02-05;SALARY;;2500.00 `) acc := &config.Account{Slug: "checking", Currency: "EUR", Parser: "csv", CSV: &config.CSVConfig{ Delimiter: ";", Date: config.Column{Col: 0, Layout: "2006-01-02"}, Description: config.Column{Col: 1}, Debit: &config.Column{Col: 2}, Credit: &config.Column{Col: 3}, }} p, err := For(acc) if err != nil { t.Fatal(err) } got, err := p.Parse(path, acc) if err != nil { t.Fatal(err) } if len(got) != 2 { t.Fatalf("got %d txns, want 2: %+v", len(got), got) } if got[0].AmountMinor != -80000 { t.Errorf("debit row = %d, want -80000", got[0].AmountMinor) } if got[1].AmountMinor != 250000 { t.Errorf("credit row = %d, want 250000", got[1].AmountMinor) } } func TestCSVConfigErrors(t *testing.T) { cases := map[string]*config.Account{ "no csv section": {Slug: "a", Parser: "csv"}, "no amount columns": {Slug: "a", Parser: "csv", CSV: &config.CSVConfig{ Date: config.Column{Layout: "2006-01-02"}, }}, "amount and debit together": {Slug: "a", Parser: "csv", CSV: &config.CSVConfig{ Date: config.Column{Layout: "2006-01-02"}, Amount: &config.Column{Col: 1}, Debit: &config.Column{Col: 2}, }}, "no date layout": {Slug: "a", Parser: "csv", CSV: &config.CSVConfig{ Amount: &config.Column{Col: 1}, }}, } for name, acc := range cases { if _, err := For(acc); err == nil { t.Errorf("%s: expected an error", name) } } } func TestUnknownParser(t *testing.T) { if _, err := For(&config.Account{Slug: "a", Parser: "nope"}); err == nil { t.Error("expected an error for an unregistered parser") } }