hi-server/internal/logic/public/user/deleteAccountLogic_test.go
2026-01-30 22:15:17 -08:00

302 lines
8.7 KiB
Go

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
// Check Old User deleted
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")
// Device record (ID 10) should PRESERVED but have a NEW user_id
var updatedDevice user.Device
err = db.Model(&user.Device{}).Where("id = ?", 10).First(&updatedDevice).Error
assert.NoError(t, err, "Device record should still exist")
assert.NotEqual(t, int64(1), updatedDevice.UserId, "Device should have a new user ID")
assert.Equal(t, "device1_id", updatedDevice.Identifier, "Device identifier should remain unchanged")
// Check AuthMethod updated
var updatedAuth user.AuthMethods
err = db.Where("auth_type = ? AND auth_identifier = ?", "device", "device1_id").First(&updatedAuth).Error
assert.NoError(t, err, "AuthMethod should still exist")
assert.Equal(t, updatedDevice.UserId, updatedAuth.UserId, "AuthMethod should link to new user ID")
}
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")
// Device record (ID 20) should PRESERVED with NEW user_id
var updatedDevice user.Device
err = db.Model(&user.Device{}).Where("id = ?", 20).First(&updatedDevice).Error
assert.NoError(t, err, "Device record should still exist")
assert.NotEqual(t, int64(2), updatedDevice.UserId, "Device should have a new user ID")
// Device Auth should PRESERVED with NEW user_id
var updatedAuthDevice user.AuthMethods
err = db.Model(&user.AuthMethods{}).Where("auth_type = 'device' AND auth_identifier = 'device2_id'").First(&updatedAuthDevice).Error
assert.NoError(t, err, "Device auth should still exist")
assert.Equal(t, updatedDevice.UserId, updatedAuthDevice.UserId)
// Email 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")
// Device 1 (ID 31) should be PRESERVED
var updatedDev1 user.Device
err = db.Model(&user.Device{}).Where("id = ?", 31).First(&updatedDev1).Error
assert.NoError(t, err, "Device 1 should still exist")
assert.NotEqual(t, int64(3), updatedDev1.UserId, "Device 1 should have new UserID")
// Device 2 (ID 32) should be PRESERVED
var updatedDev2 user.Device
err = db.Model(&user.Device{}).Where("id = ?", 32).First(&updatedDev2).Error
assert.NoError(t, err, "Device 2 should still exist")
assert.NotEqual(t, int64(3), updatedDev2.UserId, "Device 2 should have new UserID")
// Verify they are independent users
assert.NotEqual(t, updatedDev1.UserId, updatedDev2.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")
// Device record (ID 40) should be PRESERVED
var updatedDevice user.Device
err = db.Model(&user.Device{}).Where("id = ?", 40).First(&updatedDevice).Error
assert.NoError(t, err, "Device record should still exist")
assert.NotEqual(t, int64(4), updatedDevice.UserId, "Device should have a new user ID")
}