Files
money/cmd/money/main_test.go
T
nikolaandClaude Opus 5 5289250400 Key ls --uniq on the description alone
The listing keyed on account and description, so a payee seen on two accounts
was two rows. The rule builder groups by description alone and showed one, and
the two lists answer the same question -- what still needs a rule -- so the one
run from the shell overstated the work left and disagreed with the one on
screen.

A rule matches on the description and only optionally narrows to an account, so
one payee is one pattern to write however many accounts it turns up on. The
account column goes with the key; --account is still how the listing is scoped
to one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 20:10:57 +02:00

87 lines
2.8 KiB
Go

package main
import (
"strings"
"testing"
"git.petrovv.com/nikola/money/internal/model"
)
func txn(account, desc string) model.Transaction {
return model.Transaction{AccountSlug: account, Description: desc}
}
func uniqOutput(t *testing.T, limit int, txns ...model.Transaction) string {
t.Helper()
var b strings.Builder
if err := printUniq(&b, txns, limit); err != nil {
t.Fatal(err)
}
return b.String()
}
// The point of the listing is one row per pattern still to write, so fifty
// visits to one shop must not be fifty rows.
func TestUniqCollapsesRepeats(t *testing.T) {
out := uniqOutput(t, 0,
txn("checking", "LIDL SOFIA 4412"),
txn("checking", "LIDL SOFIA 4412"),
txn("checking", "ZARA"),
)
if n := strings.Count(out, "LIDL SOFIA 4412"); n != 1 {
t.Errorf("the shop appears %d times, want once:\n%s", n, out)
}
if !strings.Contains(out, "2 distinct descriptions") {
t.Errorf("count is wrong:\n%s", out)
}
}
// Descriptions are printed as a glob sees them, so two statements differing
// only in spacing or case are the one pattern they really are -- and what is
// printed is exactly what a rule written from it will match.
func TestUniqNormalisesLikeAGlob(t *testing.T) {
out := uniqOutput(t, 0,
txn("checking", "Lidl Sofia 4412"),
txn("checking", "LIDL SOFIA 4412"),
)
if !strings.Contains(out, "LIDL SOFIA 4412") {
t.Errorf("want the normalised description:\n%s", out)
}
if !strings.Contains(out, "1 distinct descriptions") {
t.Errorf("spacing and case should not make a second row:\n%s", out)
}
}
// The account is not part of the key. A rule matches on the description and
// only optionally narrows to an account, so one payee seen on two accounts is
// still one pattern to write -- and the rule builder's list groups the same
// way, which is the list this one has to agree with.
func TestUniqIgnoresTheAccount(t *testing.T) {
out := uniqOutput(t, 0,
txn("checking", "TRANSFER"),
txn("savings", "TRANSFER"),
)
if !strings.Contains(out, "1 distinct descriptions") {
t.Errorf("want one row for one description:\n%s", out)
}
if strings.Contains(out, "checking") || strings.Contains(out, "savings") {
t.Errorf("the account has no column here:\n%s", out)
}
}
// The limit caps the rows printed, and says so, rather than silently cutting
// the list down to a number that looks complete.
func TestUniqLimitReportsWhatItHeldBack(t *testing.T) {
out := uniqOutput(t, 1,
txn("checking", "ZARA"),
txn("checking", "LIDL"),
)
if !strings.Contains(out, "1 of 2 distinct descriptions") {
t.Errorf("want the total alongside the limit:\n%s", out)
}
// Alphabetical within an account, so near-identical wordings land together.
if !strings.Contains(out, "LIDL") || strings.Contains(out, "ZARA") {
t.Errorf("want the first row alphabetically:\n%s", out)
}
}