package parser_test import ( "strings" "testing" "financeiro-carvalho/internal/model" "financeiro-carvalho/internal/parser" ) const sampleOFX = `OFXHEADER:100 DATA:OFXSGML VERSION:150 DEBIT 20240315120000[-3:BRT] -150.90 2024031500001 PAGTO BOLETO ENERGIA CREDIT 20240320 5000.00 2024032000001 SALARIO EMPRESA ` func TestParseOFX_BasicFields(t *testing.T) { rows, err := parser.ParseOFX(strings.NewReader(sampleOFX)) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(rows) != 2 { t.Fatalf("expected 2 rows, got %d", len(rows)) } debit := rows[0] if debit.Date != "2024-03-15" { t.Errorf("date: want 2024-03-15, got %q", debit.Date) } if debit.Amount != 150.90 { t.Errorf("amount: want 150.90, got %v", debit.Amount) } if debit.Type != "expense" { t.Errorf("type: want expense, got %q", debit.Type) } if debit.ExternalID != "2024031500001" { t.Errorf("external_id: got %q", debit.ExternalID) } if debit.Description != "PAGTO BOLETO ENERGIA" { t.Errorf("desc: got %q", debit.Description) } credit := rows[1] if credit.Type != "income" { t.Errorf("type: want income, got %q", credit.Type) } if credit.Amount != 5000.00 { t.Errorf("amount: want 5000.00, got %v", credit.Amount) } } func TestParseOFX_EmptyFile(t *testing.T) { rows, err := parser.ParseOFX(strings.NewReader("")) if err != nil { t.Fatal(err) } if len(rows) != 0 { t.Fatalf("expected 0 rows, got %d", len(rows)) } } func TestParseCSV_Semicolon(t *testing.T) { // Brazilian bank CSV: semicolon separator, comma as decimal data := "15/03/2024;-150,90;SUPERMERCADO\n20/03/2024;5000,00;SALARIO\n" m := model.CSVMapping{ DateColumn: 0, AmountColumn: 1, DescriptionColumn: 2, DateFormat: "02/01/2006", HasHeader: false, DecimalSeparator: ",", FieldSeparator: ";", } rows, errs := parser.ParseCSV(strings.NewReader(data), m) if len(errs) != 0 { t.Fatalf("unexpected errors: %v", errs) } if len(rows) != 2 { t.Fatalf("expected 2 rows, got %d", len(rows)) } if rows[0].Date != "2024-03-15" { t.Errorf("date: got %q", rows[0].Date) } if rows[0].Amount != 150.90 { t.Errorf("amount: want 150.90, got %v", rows[0].Amount) } if rows[0].Type != "expense" { t.Errorf("type: got %q", rows[0].Type) } if rows[1].Type != "income" { t.Errorf("income type: got %q", rows[1].Type) } } func TestParseCSV_WithHeader(t *testing.T) { data := "Data;Valor;Descricao\n15/03/2024;-50,00;FARMACIA\n" m := model.CSVMapping{ DateColumn: 0, AmountColumn: 1, DescriptionColumn: 2, DateFormat: "02/01/2006", HasHeader: true, DecimalSeparator: ",", FieldSeparator: ";", } rows, errs := parser.ParseCSV(strings.NewReader(data), m) if len(errs) != 0 { t.Fatalf("errors: %v", errs) } if len(rows) != 1 { t.Fatalf("expected 1 row (header skipped), got %d", len(rows)) } if rows[0].Description != "FARMACIA" { t.Errorf("desc: got %q", rows[0].Description) } }