311 lines
8.9 KiB
Go
311 lines
8.9 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
|
|
|
|
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")
|
|
}
|