package service import ( "context" "testing" "time" quitmodel "wx_service/internal/quitcheckin/model" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupReminderTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &quitmodel.Profile{}, &quitmodel.DailyStatus{}, &quitmodel.RelapseEvent{}, &quitmodel.RewardGoal{}, &quitmodel.DreamPreset{}, &quitmodel.SupervisorBinding{}, &quitmodel.SupervisorInvite{}, &quitmodel.SupervisorReminderSetting{}, &quitmodel.SupervisorReminderLog{}, &quitmodel.HPChangeLog{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } return db } func TestReminderRunCreatesLogAfterNotifyTime(t *testing.T) { t.Parallel() db := setupReminderTestDB(t) svc := NewService(db) ctx := context.Background() ownerUID := 4001 supervisorUID := 4002 startDate := time.Date(2026, 4, 10, 0, 0, 0, 0, time.Local) now := time.Date(2026, 4, 16, 21, 30, 0, 0, time.Local) // seed quit profile if err := db.Create(&quitmodel.Profile{ UID: ownerUID, QuitStartDate: startDate, PackPriceCent: 2500, BaselineCigsPerDay: 10, NotifyTime: "21:00", ResetRule: "relapse_clears_streak", }).Error; err != nil { t.Fatalf("seed profile: %v", err) } // seed binding if err := db.Create(&quitmodel.SupervisorBinding{ OwnerUID: ownerUID, SupervisorUID: supervisorUID, Status: "active", }).Error; err != nil { t.Fatalf("seed binding: %v", err) } // enable reminder if err := db.Create(&quitmodel.SupervisorReminderSetting{ OwnerUID: ownerUID, Enabled: true, NotifyTime: "21:00", MaxPerDay: 1, ChannelHint: "stub", }).Error; err != nil { t.Fatalf("seed setting: %v", err) } res, err := svc.RunMissedCheckinRemindersForSupervisor(ctx, supervisorUID, now) if err != nil { t.Fatalf("run: %v", err) } if res.Created != 1 { t.Fatalf("created=%d want=1", res.Created) } // run again should be rate-limited res2, err := svc.RunMissedCheckinRemindersForSupervisor(ctx, supervisorUID, now.Add(10*time.Minute)) if err != nil { t.Fatalf("run2: %v", err) } if res2.Created != 0 { t.Fatalf("created2=%d want=0", res2.Created) } } func TestReminderRunSkipsBeforeNotifyTime(t *testing.T) { t.Parallel() db := setupReminderTestDB(t) svc := NewService(db) ctx := context.Background() ownerUID := 4011 supervisorUID := 4012 startDate := time.Date(2026, 4, 10, 0, 0, 0, 0, time.Local) now := time.Date(2026, 4, 16, 20, 50, 0, 0, time.Local) if err := db.Create(&quitmodel.Profile{ UID: ownerUID, QuitStartDate: startDate, PackPriceCent: 2500, BaselineCigsPerDay: 10, NotifyTime: "21:00", ResetRule: "relapse_clears_streak", }).Error; err != nil { t.Fatalf("seed profile: %v", err) } if err := db.Create(&quitmodel.SupervisorBinding{ OwnerUID: ownerUID, SupervisorUID: supervisorUID, Status: "active", }).Error; err != nil { t.Fatalf("seed binding: %v", err) } if err := db.Create(&quitmodel.SupervisorReminderSetting{ OwnerUID: ownerUID, Enabled: true, NotifyTime: "21:00", MaxPerDay: 1, ChannelHint: "stub", }).Error; err != nil { t.Fatalf("seed setting: %v", err) } res, err := svc.RunMissedCheckinRemindersForSupervisor(ctx, supervisorUID, now) if err != nil { t.Fatalf("run: %v", err) } if res.Created != 0 { t.Fatalf("created=%d want=0", res.Created) } }