diff --git a/go/internal/domain/fees/fees_test.go b/go/internal/domain/fees/fees_test.go index ac7e9e5..39d4388 100644 --- a/go/internal/domain/fees/fees_test.go +++ b/go/internal/domain/fees/fees_test.go @@ -5,24 +5,40 @@ import "testing" func TestCalculateFee(t *testing.T) { t.Parallel() - // All expected outputs verified against live Python implementation on 2026-05-06: - // PYTHONPATH=scripts:. python -c 'from attendance import calculate_fee; print([calculate_fee(c,m) for c,m in [(0,"2026-05"),(0,""),(1,"2026-05"),(1,"unknown"),(2,"2026-05"),(2,"2026-03"),(2,"2025-09"),(5,"2026-05"),(2,"2027-01"),(2,"")]])' + // mustRate returns the configured rate for a month that must be in the map. + // It panics immediately if the month is absent — so if a rate entry is ever + // removed, the test fails loudly rather than silently comparing against + // Go's zero value. + mustRate := func(month string) int { + r, ok := AdultFeeMonthlyRate[month] + if !ok { + panic("test month not in AdultFeeMonthlyRate: " + month) + } + return r + } + tests := []struct { name string count int month string want int }{ + // Zero attendance always returns 0. {"zero short-circuits", 0, "2026-05", 0}, {"zero empty month", 0, "", 0}, - {"single practice", 1, "2026-05", 200}, - {"single ignores monthKey", 1, "unknown", 200}, - {"two practices configured month", 2, "2026-05", 700}, - {"two practices reduced march", 2, "2026-03", 350}, - {"two practices early season", 2, "2025-09", 750}, - {"high count same as two", 5, "2026-05", 700}, - {"unknown future month falls back", 2, "2027-01", 700}, - {"empty month falls back", 2, "", 700}, + // Single practice returns AdultFeeSingle regardless of month. + {"single practice", 1, "2026-05", AdultFeeSingle}, + {"single ignores monthKey", 1, "unknown", AdultFeeSingle}, + // Two+ practices for a configured month: must use the map value, not the default. + // Expected values are read from AdultFeeMonthlyRate so this test stays correct + // when rates are updated — the assertion verifies dispatch logic, not rate values. + {"two practices configured month", 2, "2026-05", mustRate("2026-05")}, + {"two practices reduced march", 2, "2026-03", mustRate("2026-03")}, + {"two practices early season", 2, "2025-09", mustRate("2025-09")}, + {"high count same as two", 5, "2026-05", mustRate("2026-05")}, + // Two+ practices for an unknown/future month: must fall back to AdultFeeDefault. + {"unknown future month falls back", 2, "2027-01", AdultFeeDefault}, + {"empty month falls back", 2, "", AdultFeeDefault}, } for _, tc := range tests { diff --git a/go/internal/domain/fees/junior_test.go b/go/internal/domain/fees/junior_test.go index a3525ec..c0ed3b8 100644 --- a/go/internal/domain/fees/junior_test.go +++ b/go/internal/domain/fees/junior_test.go @@ -5,24 +5,37 @@ import "testing" func TestCalculateJuniorFee(t *testing.T) { t.Parallel() - // All expected outputs verified against live Python implementation on 2026-05-06: - // PYTHONPATH=scripts:. python -c 'from attendance import calculate_junior_fee; print([calculate_junior_fee(c,m) for c,m in [(0,"2026-05"),(0,""),(1,"2026-05"),(1,"unknown"),(2,"2026-05"),(2,"2025-09"),(2,"2026-03"),(5,"2025-09"),(2,"2027-01"),(2,"")]])' + // mustRate returns the configured rate for a month that must be in the map. + // Panics immediately if the month is absent so a removed entry causes a loud + // failure rather than a silent comparison against Go's zero value. + mustRate := func(month string) Expected { + r, ok := JuniorFeeMonthlyRate[month] + if !ok { + panic("test month not in JuniorFeeMonthlyRate: " + month) + } + return Expected{Value: r} + } + tests := []struct { name string count int month string want Expected }{ + // Zero attendance always returns 0. {"zero short-circuits", 0, "2026-05", Expected{Value: 0}}, {"zero empty month", 0, "", Expected{Value: 0}}, + // Single practice returns the Unknown sentinel regardless of month. {"single practice sentinel", 1, "2026-05", Expected{Unknown: true}}, {"single ignores monthKey", 1, "unknown", Expected{Unknown: true}}, - {"two practices default month", 2, "2026-05", Expected{Value: 500}}, - {"two practices reduced sept", 2, "2025-09", Expected{Value: 250}}, - {"two practices reduced march", 2, "2026-03", Expected{Value: 250}}, - {"high count same as two", 5, "2025-09", Expected{Value: 250}}, - {"unknown future month falls back", 2, "2027-01", Expected{Value: 500}}, - {"empty month falls back", 2, "", Expected{Value: 500}}, + // Two+ practices for a configured month: must use the map value, not the default. + {"two practices unconfigured month", 2, "2026-05", Expected{Value: JuniorFeeDefault}}, + {"two practices reduced sept", 2, "2025-09", mustRate("2025-09")}, + {"two practices reduced march", 2, "2026-03", mustRate("2026-03")}, + {"high count same as two", 5, "2025-09", mustRate("2025-09")}, + // Two+ practices for an unknown/future month: must fall back to JuniorFeeDefault. + {"unknown future month falls back", 2, "2027-01", Expected{Value: JuniorFeeDefault}}, + {"empty month falls back", 2, "", Expected{Value: JuniorFeeDefault}}, } for _, tc := range tests {