package service import ( "context" "testing" "time" usermodel "wx_service/internal/model" quitmodel "wx_service/internal/quitcheckin/model" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupSupervisorTestDB(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( &usermodel.User{}, &quitmodel.Profile{}, &quitmodel.DailyStatus{}, &quitmodel.RelapseEvent{}, &quitmodel.HPChangeLog{}, &quitmodel.SupervisorInvite{}, &quitmodel.SupervisorBinding{}, &quitmodel.RewardGoal{}, &quitmodel.DreamPreset{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } return db } func TestSupervisorInviteBindAndOverview(t *testing.T) { t.Parallel() db := setupSupervisorTestDB(t) svc := NewService(db) ctx := context.Background() ownerUID := 3001 supervisorUID := 3002 now := time.Date(2026, 4, 16, 10, 0, 0, 0, time.Local) // seed users if err := db.Create(&usermodel.User{ID: uint(ownerUID), NickName: "owner"}).Error; err != nil { t.Fatalf("seed owner user: %v", err) } if err := db.Create(&usermodel.User{ID: uint(supervisorUID), NickName: "supervisor"}).Error; err != nil { t.Fatalf("seed supervisor user: %v", err) } startDate := time.Date(2026, 4, 10, 0, 0, 0, 0, time.Local) if _, err := svc.UpsertProfile(ctx, ownerUID, UpsertProfileRequest{ QuitStartDate: &startDate, PackPriceCent: intPtr(2500), BaselineCigsPerDay: intPtr(10), }, "owner", "", now); err != nil { t.Fatalf("upsert profile: %v", err) } invite, err := svc.CreateSupervisorInvite(ctx, ownerUID, now, 7) if err != nil { t.Fatalf("create invite: %v", err) } if invite.Token == "" { t.Fatalf("invite token empty") } if err := svc.BindSupervisorInvite(ctx, supervisorUID, invite.Token, now); err != nil { t.Fatalf("bind: %v", err) } overview, err := svc.GetSupervisorOverview(ctx, supervisorUID, now) if err != nil { t.Fatalf("overview: %v", err) } if len(overview.Items) != 1 { t.Fatalf("overview items=%d, want=1", len(overview.Items)) } if overview.Items[0].Owner.UserID != ownerUID { t.Fatalf("owner uid=%d, want=%d", overview.Items[0].Owner.UserID, ownerUID) } if overview.Items[0].Home.Summary.HPCurrent <= 0 { t.Fatalf("hp_current=%d, want > 0", overview.Items[0].Home.Summary.HPCurrent) } status, err := svc.GetSupervisorStatus(ctx, ownerUID) if err != nil { t.Fatalf("status: %v", err) } if len(status.Items) != 1 || status.Items[0].UserID != supervisorUID { t.Fatalf("status=%v, want one supervisor uid=%d", status.Items, supervisorUID) } } func TestSupervisorRevokeBindingBySupervisor(t *testing.T) { t.Parallel() db := setupSupervisorTestDB(t) svc := NewService(db) ctx := context.Background() ownerUID := 3101 supervisorUID := 3102 now := time.Date(2026, 4, 16, 10, 0, 0, 0, time.Local) if err := db.Create(&usermodel.User{ID: uint(ownerUID), NickName: "owner"}).Error; err != nil { t.Fatalf("seed owner user: %v", err) } if err := db.Create(&usermodel.User{ID: uint(supervisorUID), NickName: "supervisor"}).Error; err != nil { t.Fatalf("seed supervisor user: %v", err) } startDate := time.Date(2026, 4, 10, 0, 0, 0, 0, time.Local) if _, err := svc.UpsertProfile(ctx, ownerUID, UpsertProfileRequest{ QuitStartDate: &startDate, PackPriceCent: intPtr(2500), BaselineCigsPerDay: intPtr(10), }, "owner", "", now); err != nil { t.Fatalf("upsert profile: %v", err) } invite, err := svc.CreateSupervisorInvite(ctx, ownerUID, now, 7) if err != nil { t.Fatalf("create invite: %v", err) } if err := svc.BindSupervisorInvite(ctx, supervisorUID, invite.Token, now); err != nil { t.Fatalf("bind: %v", err) } if err := svc.RevokeSupervisorBinding(ctx, supervisorUID, ownerUID, supervisorUID, now); err != nil { t.Fatalf("revoke by supervisor: %v", err) } overview, err := svc.GetSupervisorOverview(ctx, supervisorUID, now) if err != nil { t.Fatalf("overview: %v", err) } if len(overview.Items) != 0 { t.Fatalf("overview items=%d, want=0 after revoke", len(overview.Items)) } }