package logic import ( "context" "testing" "time" "spark-server/internal/module/energy/entity" energymodel "spark-server/internal/module/energy/model" "spark-server/internal/pkg/errs" ) type fixedClock struct{ t time.Time } func (c fixedClock) Now() time.Time { return c.t } func testService(t *testing.T, when time.Time) *Service { t.Helper() s := NewService(energymodel.NewMemoryRepo()) s.withClock(fixedClock{t: when}) return s } func TestCheckInAndCalendar(t *testing.T) { when := time.Date(2026, 10, 26, 12, 0, 0, 0, time.FixedZone("CST", 8*3600)) s := testService(t, when) ctx := context.Background() const uid int64 = 1001 out, err := s.CheckIn(ctx, uid) if err != nil { t.Fatal(err) } if out.RewardPoint != 10 || out.Energy != 10 || out.Day != 20261026 { t.Fatalf("unexpected checkin: %+v", out) } if _, err := s.CheckIn(ctx, uid); err != errs.ErrDuplicate { t.Fatalf("want duplicate, got %v", err) } cal, err := s.Calendar(ctx, uid, 2026, 10) if err != nil { t.Fatal(err) } if cal.Energy != 10 || cal.ContinueDays != 1 { t.Fatalf("calendar %+v", cal) } var today *DayStatus for i := range cal.StatusList { if cal.StatusList[i].Day == 20261026 { today = &cal.StatusList[i] break } } if today == nil || today.SignInStatus != entity.StatusSigned { t.Fatalf("today status %+v", today) } var miss *DayStatus for i := range cal.StatusList { if cal.StatusList[i].Day == 20261025 { miss = &cal.StatusList[i] break } } if miss == nil || miss.SignInStatus != entity.StatusCanMakeup { t.Fatalf("miss status %+v", miss) } } func TestMakeupAndEnergyLogs(t *testing.T) { when := time.Date(2026, 10, 26, 12, 0, 0, 0, time.FixedZone("CST", 8*3600)) s := testService(t, when) ctx := context.Background() const uid int64 = 2002 q, err := s.MakeupCheck(ctx, uid, 20261025) if err != nil || !q.CanReSign { t.Fatalf("makeup check %+v %v", q, err) } res, err := s.Makeup(ctx, uid, &MakeupInput{Date: 20261025}) if err != nil { t.Fatal(err) } if res.Kind != entity.KindMakeup || res.Energy != 10 { t.Fatalf("makeup %+v", res) } if _, err := s.Makeup(ctx, uid, &MakeupInput{Date: 20261025}); err != errs.ErrForbidden && err != errs.ErrDuplicate { t.Fatalf("second makeup: %v", err) } bal, err := s.Balance(ctx, uid) if err != nil || bal.Energy != 10 { t.Fatalf("balance %+v %v", bal, err) } logs, err := s.Logs(ctx, uid, 0, 20) if err != nil || len(logs.Items) != 1 || logs.Items[0].Value != 10 { t.Fatalf("logs %+v %v", logs, err) } } func TestMakeupRejectsTodayAndOtherMonth(t *testing.T) { when := time.Date(2026, 10, 26, 12, 0, 0, 0, time.FixedZone("CST", 8*3600)) s := testService(t, when) ctx := context.Background() q, err := s.MakeupCheck(ctx, 1, 20261026) if err != nil || q.CanReSign { t.Fatalf("today should not makeup: %+v %v", q, err) } q, err = s.MakeupCheck(ctx, 1, 20260901) if err != nil || q.CanReSign { t.Fatalf("other month: %+v %v", q, err) } }