Files
gogobee/internal/plugin/adventure_holidays_test.go
prosolis a44a9d9234 Fix character sheet holiday actions, remove dead holiday prompt, cap babysit counter
- Character sheet now checks isHolidayToday() for correct remaining
  action counts on holidays
- Remove renderAdvHolidaySecondPrompt (dead code after economy split)
  and its test
- Babysit harvest counter clamped to max to prevent exceeding budget

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 15:31:25 -07:00

305 lines
8.1 KiB
Go

package plugin
import (
"strings"
"testing"
"time"
)
func TestEasterDate(t *testing.T) {
tests := []struct {
year int
month time.Month
day int
}{
{2024, time.March, 31},
{2025, time.April, 20},
{2026, time.April, 5},
{2027, time.March, 28},
{2028, time.April, 16},
}
for _, tc := range tests {
got := easterDate(tc.year)
want := time.Date(tc.year, tc.month, tc.day, 0, 0, 0, 0, time.UTC)
if !got.Equal(want) {
t.Errorf("Easter %d: got %s, want %s", tc.year, got.Format("2006-01-02"), want.Format("2006-01-02"))
}
}
}
func TestNthMonday(t *testing.T) {
tests := []struct {
desc string
year int
month time.Month
n int
want string
}{
{"Family Day 2026", 2026, time.February, 3, "2026-02-16"},
{"Labour Day 2026", 2026, time.September, 1, "2026-09-07"},
{"Thanksgiving CA 2026", 2026, time.October, 2, "2026-10-12"},
{"Family Day 2027", 2027, time.February, 3, "2027-02-15"},
}
for _, tc := range tests {
got := nthMonday(tc.year, tc.month, tc.n)
if got.Format("2006-01-02") != tc.want {
t.Errorf("%s: got %s, want %s", tc.desc, got.Format("2006-01-02"), tc.want)
}
}
}
func TestMondayBeforeMay25(t *testing.T) {
tests := []struct {
year int
want string
}{
{2026, "2026-05-18"},
{2027, "2027-05-24"},
{2028, "2028-05-22"},
}
for _, tc := range tests {
got := mondayBeforeMay25(tc.year)
if got.Format("2006-01-02") != tc.want {
t.Errorf("Victoria Day %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
}
}
}
// ── Hebrew Calendar Tests ───────────────────────────────────────────────────
func TestHebrewRoshHashanah(t *testing.T) {
// Verified against hebcal.com
tests := []struct {
year int
want string
}{
{2023, "2023-09-16"}, // 5784
{2024, "2024-10-03"}, // 5785
{2025, "2025-09-23"}, // 5786
{2026, "2026-09-12"}, // 5787
{2027, "2027-10-02"}, // 5788
{2028, "2028-09-21"}, // 5789
{2030, "2030-09-28"}, // 5791
}
for _, tc := range tests {
got := hebrewRoshHashanah(tc.year)
if got.Format("2006-01-02") != tc.want {
t.Errorf("Rosh Hashanah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
}
}
}
func TestHebrewYomKippur(t *testing.T) {
tests := []struct {
year int
want string
}{
{2026, "2026-09-21"},
{2027, "2027-10-11"},
}
for _, tc := range tests {
got := hebrewYomKippur(tc.year)
if got.Format("2006-01-02") != tc.want {
t.Errorf("Yom Kippur %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
}
}
}
func TestHebrewPassover(t *testing.T) {
// 15 Nisan (first full day of Pesach)
tests := []struct {
year int
want string
}{
{2024, "2024-04-23"},
{2025, "2025-04-13"},
{2026, "2026-04-02"},
{2027, "2027-04-22"},
}
for _, tc := range tests {
got := hebrewPassover(tc.year)
if got.Format("2006-01-02") != tc.want {
t.Errorf("Passover %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
}
}
}
func TestHebrewHanukkah(t *testing.T) {
// 25 Kislev (first full day)
tests := []struct {
year int
want string
}{
{2024, "2024-12-26"},
{2025, "2025-12-15"},
{2026, "2026-12-05"},
{2027, "2027-12-25"},
}
for _, tc := range tests {
got := hebrewHanukkah(tc.year)
if got.Format("2006-01-02") != tc.want {
t.Errorf("Hanukkah %d: got %s, want %s", tc.year, got.Format("2006-01-02"), tc.want)
}
}
}
// ── Tabular Islamic Calendar Tests ──────────────────────────────────────────
func TestIslamicEidAlFitr(t *testing.T) {
// Tabular Islamic calendar — ±1 day from observed
tests := []struct {
year int
want string
}{
{2026, "2026-03-20"},
{2027, "2027-03-10"},
}
for _, tc := range tests {
dates := tabularIslamicDates(tc.year, 10, 1)
if len(dates) == 0 {
t.Errorf("Eid al-Fitr %d: no dates returned", tc.year)
continue
}
got := dates[0].Format("2006-01-02")
if got != tc.want {
t.Errorf("Eid al-Fitr %d: got %s, want %s", tc.year, got, tc.want)
}
}
}
func TestIslamicEidAlAdha(t *testing.T) {
tests := []struct {
year int
want string
}{
{2026, "2026-05-27"},
{2027, "2027-05-17"}, // Tabular Islamic calendar ±1 day from observed
}
for _, tc := range tests {
dates := tabularIslamicDates(tc.year, 12, 10)
if len(dates) == 0 {
t.Errorf("Eid al-Adha %d: no dates returned", tc.year)
continue
}
got := dates[0].Format("2006-01-02")
if got != tc.want {
t.Errorf("Eid al-Adha %d: got %s, want %s", tc.year, got, tc.want)
}
}
}
// ── Integration Tests ───────────────────────────────────────────────────────
func TestFloatingHolidays_AllComputed(t *testing.T) {
for year := 2024; year <= 2035; year++ {
holidays := floatingHolidays(year)
if len(holidays) < 14 { // at least: 4 Easter + Nowruz + 4 Canadian + 4 Hebrew + 2 Islamic
t.Errorf("year %d: only %d floating holidays, expected at least 14", year, len(holidays))
}
// Verify no zero-time dates (would indicate a computation failure)
for _, h := range holidays {
if h.Date.IsZero() {
t.Errorf("year %d: holiday %q has zero date", year, h.Name)
}
if h.Date.Year() != year {
t.Errorf("year %d: holiday %q falls in %d", year, h.Name, h.Date.Year())
}
}
}
}
func TestFloatingHolidays_DateCollisions(t *testing.T) {
holidays := floatingHolidays(2026)
seen := make(map[string][]string)
for _, h := range holidays {
key := h.Date.Format("2006-01-02")
seen[key] = append(seen[key], h.Name)
}
for date, names := range seen {
if len(names) > 1 {
t.Logf("NOTE: %d holidays on %s: %s", len(names), date, strings.Join(names, ", "))
}
}
}
func TestFixedHolidays_Christmas(t *testing.T) {
found := false
for _, h := range majorHolidays {
if h.Month == 12 && h.Day == 25 {
found = true
break
}
}
if !found {
t.Error("Christmas not in majorHolidays")
}
}
func TestFixedHolidays_NoDuplicateMonthDay(t *testing.T) {
seen := make(map[[2]int]string)
for _, h := range majorHolidays {
key := [2]int{h.Month, h.Day}
if prev, ok := seen[key]; ok {
t.Errorf("duplicate fixed holiday on %d/%d: %s and %s", h.Month, h.Day, prev, h.Name)
}
seen[key] = h.Name
}
}
func TestDailySummary_HolidayBlock(t *testing.T) {
players := []AdvPlayerDaySummary{
{DisplayName: "Alice", Activity: "dungeon", Location: "Cellar", Outcome: "success", LootValue: 500, HolidayActions: 2},
{DisplayName: "Bob", Activity: "mine", Location: "Quarry", Outcome: "success", LootValue: 300, HolidayActions: 1},
{DisplayName: "Carol", IsDead: true, DeadUntil: "14:00 UTC", Activity: "dungeon", Location: "Cave", Outcome: "death", HolidayActions: 1},
}
text := renderAdvDailySummary("2026-12-25", nil, TwinBeeRewardSummary{}, players, "Christmas")
if !strings.Contains(text, "Christmas") {
t.Error("summary should contain holiday name")
}
if !strings.Contains(text, "two actions today") {
t.Error("summary should mention two actions on holidays")
}
if !strings.Contains(text, "before their second action") {
t.Error("summary should note Carol died before second action")
}
// Without holiday
textNoHol := renderAdvDailySummary("2026-12-26", nil, TwinBeeRewardSummary{}, players, "")
if strings.Contains(textNoHol, "two actions") {
t.Error("non-holiday summary should NOT mention two actions")
}
}
// ── JDN Roundtrip Test ──────────────────────────────────────────────────────
func TestGregorianFromJDN_Roundtrip(t *testing.T) {
// Verify known JDN ↔ Gregorian conversions
tests := []struct {
jdn int
date string
}{
{2451545, "2000-01-01"}, // J2000.0
{2440588, "1970-01-01"}, // Unix epoch
{2299161, "1582-10-15"}, // Gregorian calendar adoption
}
for _, tc := range tests {
got := gregorianFromJDN(tc.jdn)
if got.Format("2006-01-02") != tc.date {
t.Errorf("JDN %d: got %s, want %s", tc.jdn, got.Format("2006-01-02"), tc.date)
}
}
}