Files
nikolaandClaude Opus 5.5 ed52be3f1d Name NLB uploads izpisek_YYYY-MM-DD
Back to the dashed date rename_izpiski.py used, so a statement the script
already named is recognised as already there when it is uploaded again,
rather than landing as a second copy under the underscored name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:49:41 +02:00

127 lines
4.4 KiB
Go

package parser
import "testing"
// A page as pdftotext -layout renders it: a header, transaction lines ending
// in amount and balance, and indented continuation lines carrying wrapped
// descriptions and the other side's IBAN.
// The description column sits at 15, which is what the script's
// CONTINUATION_INDENT tells us about the real layout.
const nlbPage = ` NLB d.d.
IZPISEK 001/2026
DATUM OPIS ZNESEK STANJE
02.01.26 PLACILO S KARTICO -45,20 1.234,56
LIDL SOFIA 4412 SI56 1234 5678 9012 345
05.01.26 PRILIV PLACE +2.500,00 3.734,56
ACME DOO
10.01.26 PRENOS NA VARCEVALNI -500,00 3.234,56
15.01.26 NEGATIVNO STANJE -3.300,00 65,44-
Stran 1
`
func TestParseNLBText(t *testing.T) {
txns, err := parseNLBText(nlbPage, 2)
if err != nil {
t.Fatal(err)
}
if len(txns) != 4 {
t.Fatalf("got %d transactions, want 4: %+v", len(txns), txns)
}
first := txns[0]
if first.Date != "2026-01-02" {
t.Errorf("date = %q, want 2026-01-02", first.Date)
}
// The continuation line is appended to the description, and the IBAN in
// its second column goes last so that it stays contiguous however many
// lines it wrapped over.
if first.Description != "PLACILO S KARTICO LIDL SOFIA 4412 SI56 1234 5678 9012 345" {
t.Errorf("description = %q", first.Description)
}
if first.AmountMinor != -4520 {
t.Errorf("amount = %d, want -4520", first.AmountMinor)
}
if first.BalanceMinor == nil || *first.BalanceMinor != 123456 {
t.Errorf("balance = %v, want 123456", first.BalanceMinor)
}
if txns[1].AmountMinor != 250000 {
t.Errorf("credit amount = %d, want 250000", txns[1].AmountMinor)
}
if txns[1].Description != "PRILIV PLACE ACME DOO" {
t.Errorf("description = %q", txns[1].Description)
}
// A transaction with no continuation line gets nothing appended.
if txns[2].Description != "PRENOS NA VARCEVALNI" {
t.Errorf("description = %q, want no trailing account number", txns[2].Description)
}
// A trailing minus marks a negative balance.
if txns[3].BalanceMinor == nil || *txns[3].BalanceMinor != -6544 {
t.Errorf("balance = %v, want -6544", txns[3].BalanceMinor)
}
}
// A wrapped line at the top of a page must not attach to the last transaction
// of the previous page.
func TestParseNLBPageBoundary(t *testing.T) {
text := "02.01.26 FIRST -10,00 100,00\n" +
"\f" +
" STRAY CONTINUATION AT TOP OF PAGE\n" +
"03.01.26 SECOND -20,00 80,00\n" +
" REAL CONTINUATION\n"
txns, err := parseNLBText(text, 2)
if err != nil {
t.Fatal(err)
}
if len(txns) != 2 {
t.Fatalf("got %d transactions, want 2", len(txns))
}
if txns[0].Description != "FIRST" {
t.Errorf("first description = %q, want FIRST (page furniture must not leak in)", txns[0].Description)
}
if txns[1].Description != "SECOND REAL CONTINUATION" {
t.Errorf("second description = %q", txns[1].Description)
}
}
// Shallowly indented lines are footers, not wrapped descriptions.
func TestParseNLBIgnoresShallowIndent(t *testing.T) {
text := "02.01.26 FIRST -10,00 100,00\n" +
" Stran 1 od 2\n"
txns, err := parseNLBText(text, 2)
if err != nil {
t.Fatal(err)
}
if len(txns) != 1 || txns[0].Description != "FIRST" {
t.Errorf("got %+v, want a single FIRST transaction", txns)
}
}
// A date-led line without an amount and balance is a real problem, not
// something to silently drop.
func TestParseNLBRejectsMalformedLine(t *testing.T) {
text := "02.01.26 NO AMOUNT OR BALANCE HERE\n"
if _, err := parseNLBText(text, 2); err == nil {
t.Error("expected an error for a transaction line with no amount")
}
}
// An izpisek is named after its statement date, as rename_izpiski.py did.
func TestNLBStatementName(t *testing.T) {
header := " IZPISEK 001/2026\n" +
" Datum izpiska 31.01.2026 Stran 1\n"
if got := nlbStatementName(header + nlbPage); got != "izpisek_2026-01-31" {
t.Errorf("name = %q, want izpisek_2026-01-31", got)
}
if got := nlbStatementName(nlbPage); got != "" {
t.Errorf("name without a statement date = %q, want none", got)
}
}