package membership // Golden strings verified against scripts/calculate_fees.py on 2026-05-06: // // PYTHONPATH=scripts:. python scripts/calculate_fees.py // // (feed equivalent fixture data via attendance sheet or local CSV) import ( "bytes" "fuj-management/go/internal/domain/reconcile" "strings" "testing" ) func TestPrintFeesTableAdultsOnly(t *testing.T) { t.Parallel() members := []reconcile.Member{ {Name: "Alice", Tier: "A", Fees: map[string]reconcile.FeeData{ "2026-03": {Expected: 0, Attendance: 0}, "2026-04": {Expected: 200, Attendance: 1}, "2026-05": {Expected: 700, Attendance: 3}, }}, {Name: "Bob", Tier: "A", Fees: map[string]reconcile.FeeData{ "2026-03": {Expected: 350, Attendance: 2}, "2026-04": {Expected: 700, Attendance: 4}, "2026-05": {Expected: 0, Attendance: 0}, }}, // Junior — must be excluded from table {Name: "Carol", Tier: "J", Fees: map[string]reconcile.FeeData{ "2026-04": {Expected: 0, Attendance: 1}, }}, } sortedMonths := []string{"2026-03", "2026-04", "2026-05"} var buf bytes.Buffer printFeesTable(&buf, members, sortedMonths) got := buf.String() // Verify structure if !strings.Contains(got, "Member") { t.Error("missing header 'Member'") } if !strings.Contains(got, "Mar 2026") || !strings.Contains(got, "Apr 2026") || !strings.Contains(got, "May 2026") { t.Error("missing month labels") } if strings.Contains(got, "Carol") { t.Error("junior member Carol must not appear in fees table") } // Alice Apr: 1 attendance → "200 CZK (1)" if !strings.Contains(got, "200 CZK (1)") { t.Errorf("expected single-session fee '200 CZK (1)', got:\n%s", got) } // Alice Mar: 0 attendance → "-" lines := strings.Split(got, "\n") aliceLine := "" for _, l := range lines { if strings.HasPrefix(strings.TrimSpace(l), "Alice") { aliceLine = l break } } if aliceLine == "" { t.Fatal("no Alice line found") } // Alice's first col (Mar 2026) should be "-" if !strings.Contains(aliceLine, "-") { t.Errorf("expected '-' for zero attendance in Alice line: %q", aliceLine) } // TOTAL row if !strings.Contains(got, "TOTAL") { t.Error("missing TOTAL row") } // Total for May 2026 = 700 CZK if !strings.Contains(got, "700 CZK") { t.Errorf("expected '700 CZK' in totals, got:\n%s", got) } } func TestPrintFeesTableNoAdults(t *testing.T) { t.Parallel() members := []reconcile.Member{ {Name: "X", Tier: "J", Fees: map[string]reconcile.FeeData{}}, } var buf bytes.Buffer printFeesTable(&buf, members, []string{"2026-04"}) if buf.String() != "No data.\n" { t.Errorf("want 'No data.', got %q", buf.String()) } } func TestPrintFeesTableEmpty(t *testing.T) { t.Parallel() var buf bytes.Buffer printFeesTable(&buf, nil, nil) if buf.String() != "No data.\n" { t.Errorf("want 'No data.', got %q", buf.String()) } }