package user import ( "context" "fmt" "os" "testing" "github.com/alicebob/miniredis/v2" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/constant" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func createTestSvcCtx(t *testing.T, testName string) (*svc.ServiceContext, *gorm.DB, *miniredis.Miniredis) { // 1. Setup Miniredis mr, err := miniredis.Run() assert.NoError(t, err) rdb := redis.NewClient(&redis.Options{ Addr: mr.Addr(), }) // 2. Setup GORM with SQLite (File based for reliability) dbName := fmt.Sprintf("test_%s.db", testName) db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{}) assert.NoError(t, err) t.Cleanup(func() { os.Remove(dbName) }) // Migrate tables (Using Migrator to bypass index collision errors on global schema in SQLite) _ = db.Migrator().CreateTable(&user.User{}) _ = db.Migrator().CreateTable(&user.Device{}) _ = db.Migrator().CreateTable(&user.AuthMethods{}) _ = db.Migrator().CreateTable(&user.Subscribe{}) // 3. Create ServiceContext c := config.Config{} c.Invite.OnlyFirstPurchase = true svcCtx := &svc.ServiceContext{ Redis: rdb, DB: db, Config: c, UserModel: user.NewModel(db, rdb), } return svcCtx, db, mr } func TestDeleteAccount_Guest_SingleDevice(t *testing.T) { svcCtx, db, mr := createTestSvcCtx(t, t.Name()) defer mr.Close() // Setup: User, 1 Device, No Email u := &user.User{ Id: 1, ReferCode: "ref1", } db.Create(u) device := &user.Device{ Id: 10, UserId: 1, Identifier: "device1_id", } db.Create(device) auth := &user.AuthMethods{ UserId: 1, AuthType: "device", AuthIdentifier: "device1_id", } db.Create(auth) // Context ctx := context.Background() ctx = context.WithValue(ctx, constant.CtxKeyUser, u) ctx = context.WithValue(ctx, constant.CtxKeyDeviceID, int64(10)) ctx = context.WithValue(ctx, constant.CtxKeySessionID, "session1") // Run Logic l := NewDeleteAccountLogic(ctx, svcCtx) resp, err := l.DeleteAccountAll() if err != nil { t.Fatalf("DeleteAccountAll failed: %v", err) } assert.True(t, resp.Success) // Assertions for Guest User (Should be DELETED) // Because 1 auth (device) and 1 device count -> isMainAccount = false var userCount int64 db.Model(&user.User{}).Where("refer_code = ?", "ref1").Count(&userCount) assert.Equal(t, int64(0), userCount, "Old User (by refer code) should be deleted") // Old device record (ID 10) should be gone var deviceCount int64 db.Model(&user.Device{}).Where("id = ?", 10).Count(&deviceCount) assert.Equal(t, int64(0), deviceCount, "Old device record should be deleted") // Check if new user created for the device var newDeviceCount int64 db.Model(&user.Device{}).Where("identifier = ?", "device1_id").Count(&newDeviceCount) assert.Equal(t, int64(1), newDeviceCount, "New device record should be created") } func TestDeleteAccount_User_WithEmail(t *testing.T) { svcCtx, db, mr := createTestSvcCtx(t, t.Name()) defer mr.Close() // Setup: User, 1 Device, 1 Email u := &user.User{ Id: 2, } db.Create(u) device := &user.Device{ Id: 20, UserId: 2, Identifier: "device2_id", } db.Create(device) authDevice := &user.AuthMethods{ UserId: 2, AuthType: "device", AuthIdentifier: "device2_id", } db.Create(authDevice) authEmail := &user.AuthMethods{ UserId: 2, AuthType: "email", AuthIdentifier: "test@example.com", } db.Create(authEmail) // Context ctx := context.Background() ctx = context.WithValue(ctx, constant.CtxKeyUser, u) ctx = context.WithValue(ctx, constant.CtxKeyDeviceID, int64(20)) ctx = context.WithValue(ctx, constant.CtxKeySessionID, "session2") // Run Logic l := NewDeleteAccountLogic(ctx, svcCtx) resp, err := l.DeleteAccountAll() if err != nil { t.Fatalf("DeleteAccountAll failed: %v", err) } assert.True(t, resp.Success) // Assertions for Email User (Should BE deleted now) var userCount int64 db.Model(&user.User{}).Where("id = ?", 2).Count(&userCount) assert.Equal(t, int64(0), userCount, "User should be deleted") var deviceCount int64 db.Model(&user.Device{}).Where("id = ?", 20).Count(&deviceCount) assert.Equal(t, int64(0), deviceCount, "Old device record should be deleted") var authDeviceCount int64 db.Model(&user.AuthMethods{}).Where("user_id = ? AND auth_type = 'device'", 2).Count(&authDeviceCount) assert.Equal(t, int64(0), authDeviceCount, "Device auth should be removed") var authEmailCount int64 db.Model(&user.AuthMethods{}).Where("user_id = ? AND auth_type = 'email'", 2).Count(&authEmailCount) assert.Equal(t, int64(0), authEmailCount, "Email auth should be removed") } func TestDeleteAccount_User_MultiDevice(t *testing.T) { svcCtx, db, mr := createTestSvcCtx(t, t.Name()) defer mr.Close() // Setup: User, 2 Devices u := &user.User{ Id: 3, } db.Create(u) // Device 1 (Current) device1 := &user.Device{ Id: 31, UserId: 3, Identifier: "device3_1", } db.Create(device1) auth1 := &user.AuthMethods{ UserId: 3, AuthType: "device", AuthIdentifier: "device3_1", } db.Create(auth1) // Device 2 (Other) device2 := &user.Device{ Id: 32, UserId: 3, Identifier: "device3_2", } db.Create(device2) auth2 := &user.AuthMethods{ UserId: 3, AuthType: "device", AuthIdentifier: "device3_2", } db.Create(auth2) // Context ctx := context.Background() ctx = context.WithValue(ctx, constant.CtxKeyUser, u) ctx = context.WithValue(ctx, constant.CtxKeyDeviceID, int64(31)) // Current = Device 1 ctx = context.WithValue(ctx, constant.CtxKeySessionID, "session3") // Run Logic l := NewDeleteAccountLogic(ctx, svcCtx) resp, err := l.DeleteAccountAll() if err != nil { t.Fatalf("DeleteAccountAll failed: %v", err) } assert.True(t, resp.Success) // Assertions for Multi Device User (Should BE deleted now) var userCount int64 db.Model(&user.User{}).Where("id = ?", 3).Count(&userCount) assert.Equal(t, int64(0), userCount, "Old User should be deleted") // Old device records should be deleted var device1Count int64 db.Model(&user.Device{}).Where("id = ?", 31).Count(&device1Count) assert.Equal(t, int64(0), device1Count, "Old device 1 record should be deleted") var device2Count int64 db.Model(&user.Device{}).Where("id = ?", 32).Count(&device2Count) assert.Equal(t, int64(0), device2Count, "Old device 2 record should be deleted") // NEW Device records should be created var newDevice1Count int64 db.Model(&user.Device{}).Where("identifier = ?", "device3_1").Count(&newDevice1Count) assert.Equal(t, int64(1), newDevice1Count, "New Device 1 should be created") var newDevice2Count int64 db.Model(&user.Device{}).Where("identifier = ?", "device3_2").Count(&newDevice2Count) assert.Equal(t, int64(1), newDevice2Count, "New Device 2 should be created") // Verify they are anonymous (different users, assuming registerUserAndDevice creates new user each time) var newDev1 user.Device db.Where("identifier = ?", "device3_1").First(&newDev1) assert.NotEqual(t, int64(3), newDev1.UserId, "New Device 1 should have new UserID") var newDev2 user.Device db.Where("identifier = ?", "device3_2").First(&newDev2) assert.NotEqual(t, int64(3), newDev2.UserId, "New Device 2 should have new UserID") assert.NotEqual(t, newDev1.UserId, newDev2.UserId, "Devices should have independent user accounts") } func TestDeleteAccount_MissingDeviceID(t *testing.T) { svcCtx, db, mr := createTestSvcCtx(t, t.Name()) defer mr.Close() // Setup: User, 1 Device, but context missing DeviceID u := &user.User{ Id: 4, ReferCode: "ref4", } db.Create(u) device := &user.Device{ Id: 40, UserId: 4, Identifier: "device4_id", } db.Create(device) // Context (Missing DeviceID) ctx := context.Background() ctx = context.WithValue(ctx, constant.CtxKeyUser, u) ctx = context.WithValue(ctx, constant.CtxKeySessionID, "session4") // Run Logic l := NewDeleteAccountLogic(ctx, svcCtx) resp, err := l.DeleteAccountAll() if err != nil { t.Fatalf("DeleteAccountAll failed: %v", err) } assert.True(t, resp.Success) // Assertions: User should be deleted var userCount int64 db.Model(&user.User{}).Where("refer_code = ?", "ref4").Count(&userCount) assert.Equal(t, int64(0), userCount, "User should be deleted even without device context") // Old device should be deleted var deviceCount int64 db.Model(&user.Device{}).Where("id = ?", 40).Count(&deviceCount) assert.Equal(t, int64(0), deviceCount, "Old device record should be deleted") // New device should be created for the orphaned device var newDeviceCount int64 db.Model(&user.Device{}).Where("identifier = ?", "device4_id").Count(&newDeviceCount) assert.Equal(t, int64(1), newDeviceCount, "Device should be reset to new account even if caller didn't specify deviceID") }