package domain import ( "testing" "time" ) func d(y int, m time.Month, day int) time.Time { return time.Date(y, m, day, 0, 0, 0, 0, time.Local) } func TestDayDiff(t *testing.T) { cases := []struct { a, b time.Time want int }{ {d(2026, 1, 1), d(2026, 1, 1), 0}, {d(2026, 1, 1), d(2026, 1, 31), 30}, {d(2026, 2, 28), d(2026, 3, 1), 1}, {d(2024, 2, 28), d(2024, 3, 1), 2}, // 闰年 {d(2026, 12, 31), d(2027, 1, 1), 1}, {d(2026, 1, 10), d(2026, 1, 1), -9}, } for _, c := range cases { if got := DayDiff(c.a, c.b); got != c.want { t.Errorf("DayDiff(%s,%s)=%d want %d", Date(c.a), Date(c.b), got, c.want) } } } func TestEvalDay(t *testing.T) { today := d(2026, 10, 18) cases := []struct { name string status string end string wantState string wantLevel string wantDays int }{ {"进行中", "active", "2026-11-16", StateActive, LevelOK, 30}, {"今天到期", "active", "2026-10-18", StateActive, LevelToday, 1}, {"3天内", "active", "2026-10-20", StateActive, LevelWarn, 3}, {"已到期", "active", "2026-10-17", StateExpired, LevelExpired, 0}, {"暂停中", "paused", "2026-11-16", StatePaused, LevelNone, 30}, {"已结束", "closed", "2026-11-16", StateClosed, LevelNone, 30}, } for _, c := range cases { e := EvalSub(c.status, "2026-10-01", c.end, today, 3) if e.State != c.wantState { t.Errorf("%s: state=%s want %s", c.name, e.State, c.wantState) } if e.Level != c.wantLevel { t.Errorf("%s: level=%s want %s", c.name, e.Level, c.wantLevel) } if e.RemainingDays != c.wantDays { t.Errorf("%s: remaining=%d want %d", c.name, e.RemainingDays, c.wantDays) } } } func TestEvalDayNotStarted(t *testing.T) { today := d(2026, 10, 18) e := EvalSub("active", "2026-10-20", "2026-11-18", today, 3) if e.State != StatePending { t.Fatalf("state=%s want pending", e.State) } if e.RemainingDays != 32 { t.Fatalf("remaining=%d want 32", e.RemainingDays) } } // TestEvalNoEndDate 覆盖历史数据里结束日为空的兜底分支(lite 版没有按次计费)。 func TestEvalNoEndDate(t *testing.T) { today := d(2026, 10, 18) e := EvalSub("active", "2026-10-01", "", today, 3) if e.Level != LevelNone || e.DaysLeft != -1 || e.RemainingDays != 0 { t.Fatalf("level=%s daysLeft=%d remain=%d", e.Level, e.DaysLeft, e.RemainingDays) } if e.State != StateActive { t.Fatalf("state=%s want active", e.State) } } func TestPlanExtend(t *testing.T) { today := d(2026, 10, 18) // 未到期:从原结束日次日顺延,追加天数 = 新旧结束日之差 oldEnd := d(2026, 11, 16) newEnd := d(2026, 12, 2) p := PlanExtend(oldEnd, newEnd, today) if Date(p.Base) != "2026-11-17" { t.Fatalf("base=%s", Date(p.Base)) } if p.Days != 16 { t.Fatalf("days=%d want 16", p.Days) } // 已到期:从今天起算 oldEnd = d(2026, 10, 5) newEnd = d(2026, 11, 3) p = PlanExtend(oldEnd, newEnd, today) if Date(p.Base) != "2026-10-18" { t.Fatalf("base=%s", Date(p.Base)) } if p.Days != 17 { t.Fatalf("days=%d want 17", p.Days) } // 选的日期早于计算起点 → 无效 p = PlanExtend(d(2026, 11, 16), d(2026, 11, 10), today) if !p.Backwards || p.Days != 0 { t.Fatalf("backwards=%v days=%d", p.Backwards, p.Days) } } func TestMonthHelpers(t *testing.T) { if got := Date(MonthEnd(2026, time.February)); got != "2026-02-28" { t.Fatalf("MonthEnd=%s", got) } if got := Date(MonthEnd(2024, time.February)); got != "2024-02-29" { t.Fatalf("MonthEnd=%s", got) } if got := WeekdayIndex(d(2026, 10, 18)); got != 6 { // 周日 t.Fatalf("WeekdayIndex=%d want 6", got) } if got := WeekdayIndex(d(2026, 10, 19)); got != 0 { // 周一 t.Fatalf("WeekdayIndex=%d want 0", got) } }