diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b7b5379..8d6b1e7 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -25,7 +25,8 @@ "Bash(tasklist:*)", "Bash(dir:*)", "Bash(findstr:*)", - "Bash(netstat:*)" + "Bash(netstat:*)", + "Bash(set CGO_ENABLED=1)" ] } } diff --git a/backend/go.mod b/backend/go.mod index e8a4022..36a734b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,8 +4,10 @@ go 1.25.0 require ( github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/stretchr/testify v1.11.1 github.com/zeromicro/go-zero v1.9.4 gorm.io/driver/mysql v1.6.0 + gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 ) @@ -14,6 +16,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -28,9 +31,11 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect @@ -56,4 +61,5 @@ require ( google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index fa273f0..d008706 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -51,6 +51,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= @@ -140,6 +142,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= diff --git a/backend/internal/logic/profile/changepasswordlogic_test.go b/backend/internal/logic/profile/changepasswordlogic_test.go new file mode 100644 index 0000000..afe0208 --- /dev/null +++ b/backend/internal/logic/profile/changepasswordlogic_test.go @@ -0,0 +1,274 @@ +package profile + +import ( + "context" + "crypto/md5" + "fmt" + "testing" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.ChangePasswordRequest{} + +// TestChangePassword_Success 测试成功修改密码 +func TestChangePassword_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户,密码为 "password" + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 200, resp.Code) + assert.Equal(t, "修改密码成功", resp.Message) + + // 验证数据库中的密码已更新 + updatedUser, err := model.FindOne(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + + newEncrypted := fmt.Sprintf("%x", md5.Sum([]byte(req.NewPassword))) + assert.Equal(t, newEncrypted, updatedUser.Password) +} + +// TestChangePassword_WrongOldPassword 测试旧密码错误 +func TestChangePassword_WrongOldPassword(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 - 使用错误的旧密码 + req := &types.ChangePasswordRequest{ + OldPassword: "wrongpassword", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 400, resp.Code) + assert.Equal(t, "旧密码错误", resp.Message) + + // 验证数据库中的密码没有改变 + updatedUser, err := model.FindOne(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + assert.Equal(t, user.Password, updatedUser.Password) +} + +// TestChangePassword_NoUserIdInContext 测试上下文中没有用户ID +func TestChangePassword_NoUserIdInContext(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + createTestUser(t, svcCtx.DB) + + // 设置上下文,不包含用户ID + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "未获取到用户信息") +} + +// TestChangePassword_InvalidUserIdType 测试用户ID类型错误 +func TestChangePassword_InvalidUserIdType(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + createTestUser(t, svcCtx.DB) + + // 设置上下文,用户ID类型错误 + ctx := context.WithValue(context.Background(), "userId", "invalid") + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户ID格式错误") +} + +// TestChangePassword_UserNotFound 测试用户不存在 +func TestChangePassword_UserNotFound(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 设置上下文,使用不存在的用户ID + ctx := context.WithValue(context.Background(), "userId", int64(99999)) + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestChangePassword_SamePassword 测试使用相同的新密码 +func TestChangePassword_SamePassword(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 - 新密码与旧密码相同 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "password", + } + + // 执行测试 - 虽然相同,但从逻辑上应该允许 + resp, err := logic.ChangePassword(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 200, resp.Code) + + // 验证密码加密后的值仍然一致 + updatedUser, err := model.FindOne(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + assert.Equal(t, user.Password, updatedUser.Password) +} + +// TestChangePassword_MultipleChanges 测试多次修改密码 +func TestChangePassword_MultipleChanges(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + passwords := []string{"password", "newpass1", "newpass2", "newpass3"} + + for i := 0; i < len(passwords)-1; i++ { + logic := NewChangePasswordLogic(ctx, svcCtx) + + req := &types.ChangePasswordRequest{ + OldPassword: passwords[i], + NewPassword: passwords[i+1], + } + + resp, err := logic.ChangePassword(req) + + require.NoError(t, err, "Failed at iteration %d", i) + require.NotNil(t, resp) + assert.Equal(t, 200, resp.Code) + + // 验证密码已更新 + updatedUser, err := model.FindOne(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + + expectedEncrypted := fmt.Sprintf("%x", md5.Sum([]byte(passwords[i+1]))) + assert.Equal(t, expectedEncrypted, updatedUser.Password) + } +} + +// BenchmarkChangePassword 性能测试 +func BenchmarkChangePassword(b *testing.B) { + svcCtx, _ := setupTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + // 创建测试用户 + user := createTestUser(&testing.T{}, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + logic := NewChangePasswordLogic(ctx, svcCtx) + _, _ = logic.ChangePassword(req) + } +} diff --git a/backend/internal/logic/profile/getprofilelogic_test.go b/backend/internal/logic/profile/getprofilelogic_test.go new file mode 100644 index 0000000..8f74e44 --- /dev/null +++ b/backend/internal/logic/profile/getprofilelogic_test.go @@ -0,0 +1,243 @@ +package profile + +import ( + "context" + "testing" + "time" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.GetProfileResponse{} + +// setupTestDB 创建测试数据库并返回 ServiceContext(使用MySQL) +func setupTestDB(t *testing.T) (*svc.ServiceContext, func()) { + t.Helper() + + // 使用 MySQL 进行测试 + dsn := "root:dev123456@tcp(219.159.132.177:17173)/base?charset=utf8mb4&parseTime=true&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + require.NoError(t, err) + + // 自动迁移表 + err = db.AutoMigrate(&model.User{}, &model.Profile{}) + require.NoError(t, err) + + // 清理所有测试数据(使用 TRUNCATE 快速清空表) + db.Exec("SET FOREIGN_KEY_CHECKS = 0") + db.Exec("TRUNCATE TABLE profile") + db.Exec("TRUNCATE TABLE user") + db.Exec("SET FOREIGN_KEY_CHECKS = 1") + + // 创建测试 ServiceContext + svcCtx := &svc.ServiceContext{ + DB: db, + } + + // 清理函数 + cleanup := func() { + sqlDB, _ := db.DB() + if sqlDB != nil { + sqlDB.Close() + } + } + + return svcCtx, cleanup +} + +// createTestUser 创建测试用户 +func createTestUser(t *testing.T, db *gorm.DB) *model.User { + t.Helper() + + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "5f4dcc3b5aa765d61d8327deb882cf99", // MD5 of "password" + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + return user +} + +// createTestProfile 创建测试个人资料 +func createTestProfile(t *testing.T, db *gorm.DB, userId int64) *model.Profile { + t.Helper() + + now := time.Now() + profile := &model.Profile{ + UserId: userId, + Avatar: "http://example.com/avatar.jpg", + Bio: "This is a test bio", + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(profile).Error + require.NoError(t, err) + + return profile +} + +// TestGetProfile_Success 测试成功获取个人信息 +func TestGetProfile_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + createTestProfile(t, svcCtx.DB, user.Id) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) + assert.Equal(t, "13800138000", resp.Phone) + assert.Equal(t, "http://example.com/avatar.jpg", resp.Avatar) + assert.Equal(t, "This is a test bio", resp.Bio) + assert.Equal(t, 1, resp.Status) +} + +// TestGetProfile_NoProfile 测试用户没有个人资料的情况 +func TestGetProfile_NoProfile(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户,不创建个人资料 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 - 应该返回用户信息,但 profile 字段为空 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "", resp.Avatar) + assert.Equal(t, "", resp.Bio) +} + +// TestGetProfile_NoUserIdInContext 测试上下文中没有用户ID +func TestGetProfile_NoUserIdInContext(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + createTestUser(t, svcCtx.DB) + + // 设置上下文,不包含用户ID + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "未获取到用户信息") +} + +// TestGetProfile_InvalidUserIdType 测试用户ID类型错误 +func TestGetProfile_InvalidUserIdType(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + createTestUser(t, svcCtx.DB) + + // 设置上下文,用户ID类型错误 + ctx := context.WithValue(context.Background(), "userId", "invalid") + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户ID格式错误") +} + +// TestGetProfile_UserNotFound 测试用户不存在 +func TestGetProfile_UserNotFound(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 设置上下文,使用不存在的用户ID + ctx := context.WithValue(context.Background(), "userId", int64(99999)) + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// BenchmarkGetProfile 性能测试 +func BenchmarkGetProfile(b *testing.B) { + svcCtx, _ := setupTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + // 创建测试用户 + user := createTestUser(&testing.T{}, svcCtx.DB) + createTestProfile(&testing.T{}, svcCtx.DB, user.Id) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + logic := NewGetProfileLogic(ctx, svcCtx) + _, _ = logic.GetProfile() + } +} diff --git a/backend/internal/logic/profile/updateprofilelogic_test.go b/backend/internal/logic/profile/updateprofilelogic_test.go new file mode 100644 index 0000000..c5b9010 --- /dev/null +++ b/backend/internal/logic/profile/updateprofilelogic_test.go @@ -0,0 +1,269 @@ +package profile + +import ( + "context" + "testing" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.UpdateProfileRequest{} + +// TestUpdateProfile_Success 测试成功更新个人资料 +func TestUpdateProfile_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Username: "updateduser", + Phone: "13900139000", + Avatar: "http://example.com/newavatar.jpg", + Bio: "Updated bio text", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "updateduser", resp.Username) + assert.Equal(t, "13900139000", resp.Phone) + assert.Equal(t, "http://example.com/newavatar.jpg", resp.Avatar) + assert.Equal(t, "Updated bio text", resp.Bio) +} + +// TestUpdateProfile_CreateNewProfile 测试用户第一次更新时创建个人资料 +func TestUpdateProfile_CreateNewProfile(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户,但不创建个人资料 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Avatar: "http://example.com/avatar.jpg", + Bio: "First bio", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "http://example.com/avatar.jpg", resp.Avatar) + assert.Equal(t, "First bio", resp.Bio) + + // 验证数据库中确实创建了个人资料 + profile, err := model.FindProfileByUserId(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + require.NotNil(t, profile) + assert.Equal(t, "http://example.com/avatar.jpg", profile.Avatar) +} + +// TestUpdateProfile_UpdateExistingProfile 测试更新已存在的个人资料 +func TestUpdateProfile_UpdateExistingProfile(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户和个人资料 + user := createTestUser(t, svcCtx.DB) + createTestProfile(t, svcCtx.DB, user.Id) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 - 只更新部分字段 + req := &types.UpdateProfileRequest{ + Bio: "Updated bio only", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, "Updated bio only", resp.Bio) + assert.Equal(t, "http://example.com/avatar.jpg", resp.Avatar) // 保持不变 + + // 验证数据库中确实更新了 + profile, err := model.FindProfileByUserId(context.Background(), svcCtx.DB, user.Id) + require.NoError(t, err) + assert.Equal(t, "Updated bio only", profile.Bio) + assert.Equal(t, "http://example.com/avatar.jpg", profile.Avatar) +} + +// TestUpdateProfile_UpdateOnlyUsername 测试只更新用户名 +func TestUpdateProfile_UpdateOnlyUsername(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 - 只更新用户名 + req := &types.UpdateProfileRequest{ + Username: "newusername", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, "newusername", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) // 保持不变 +} + +// TestUpdateProfile_NoUserIdInContext 测试上下文中没有用户ID +func TestUpdateProfile_NoUserIdInContext(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + createTestUser(t, svcCtx.DB) + + // 设置上下文,不包含用户ID + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Username: "updateduser", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "未获取到用户信息") +} + +// TestUpdateProfile_UserNotFound 测试用户不存在 +func TestUpdateProfile_UserNotFound(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 设置上下文,使用不存在的用户ID + ctx := context.WithValue(context.Background(), "userId", int64(99999)) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Username: "updateduser", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestUpdateProfile_EmptyRequest 测试空请求 +func TestUpdateProfile_EmptyRequest(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备空请求数据 + req := &types.UpdateProfileRequest{} + + // 执行测试 - 应该正常工作,只是不做任何更新 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + // 验证数据没有变化 + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "13800138000", resp.Phone) +} + +// BenchmarkUpdateProfile 性能测试 +func BenchmarkUpdateProfile(b *testing.B) { + svcCtx, _ := setupTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + // 创建测试用户 + user := createTestUser(&testing.T{}, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Username: "updateduser", + Bio: "Updated bio", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + logic := NewUpdateProfileLogic(ctx, svcCtx) + _, _ = logic.UpdateProfile(req) + } +} diff --git a/backend/internal/logic/user/createuserlogic_test.go b/backend/internal/logic/user/createuserlogic_test.go new file mode 100644 index 0000000..c4bf4ed --- /dev/null +++ b/backend/internal/logic/user/createuserlogic_test.go @@ -0,0 +1,238 @@ +package user + +import ( + "context" + "testing" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// setupUserTestDB 创建测试数据库并返回 ServiceContext(使用MySQL) +func setupUserTestDB(t *testing.T) (*svc.ServiceContext, func()) { + t.Helper() + + // 使用 MySQL 进行测试 + dsn := "root:dev123456@tcp(219.159.132.177:17173)/base?charset=utf8mb4&parseTime=true&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + require.NoError(t, err) + + // 自动迁移表 + err = db.AutoMigrate(&model.User{}, &model.Profile{}) + require.NoError(t, err) + + // 清理所有测试数据(使用 TRUNCATE 快速清空表) + db.Exec("SET FOREIGN_KEY_CHECKS = 0") + db.Exec("TRUNCATE TABLE profile") + db.Exec("TRUNCATE TABLE user") + db.Exec("SET FOREIGN_KEY_CHECKS = 1") + + // 创建测试 ServiceContext + svcCtx := &svc.ServiceContext{ + DB: db, + } + + // 清理函数 + cleanup := func() { + sqlDB, _ := db.DB() + if sqlDB != nil { + sqlDB.Close() + } + } + + return svcCtx, cleanup +} + +// TestCreateUser_Success 测试成功创建用户 +func TestCreateUser_Success(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewCreateUserLogic(ctx, svcCtx) + + // 准备创建用户请求数据 + req := &types.CreateUserRequest{ + Username: "testuser", + Email: "test@example.com", + Password: "password123", + Phone: "13800138000", + } + + // 执行测试 + resp, err := logic.CreateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Greater(t, resp.Id, int64(0)) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) + assert.Equal(t, "13800138000", resp.Phone) + assert.Equal(t, 1, resp.Status) + assert.NotEmpty(t, resp.CreatedAt) + assert.NotEmpty(t, resp.UpdatedAt) +} + +// TestCreateUser_DuplicateEmail 测试邮箱重复 +func TestCreateUser_DuplicateEmail(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建第一个用户 + ctx := context.Background() + logic := NewCreateUserLogic(ctx, svcCtx) + + req1 := &types.CreateUserRequest{ + Username: "user1", + Email: "test@example.com", + Password: "password123", + } + + _, err := logic.CreateUser(req1) + require.NoError(t, err) + + // 尝试创建相同邮箱的第二个用户 + logic2 := NewCreateUserLogic(ctx, svcCtx) + + req2 := &types.CreateUserRequest{ + Username: "user2", + Email: "test@example.com", // 相同邮箱 + Password: "password456", + } + + resp, err := logic2.CreateUser(req2) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "邮箱已被注册") +} + +// TestCreateUser_MinimumFields 测试使用最少字段创建用户 +func TestCreateUser_MinimumFields(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewCreateUserLogic(ctx, svcCtx) + + // 准备创建用户请求数据 - 只填必填字段 + req := &types.CreateUserRequest{ + Username: "testuser", + Email: "test@example.com", + Password: "password123", + // Phone 字段可选,不填写 + } + + // 执行测试 + resp, err := logic.CreateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) + assert.Equal(t, "", resp.Phone) // Phone 应该为空字符串 +} + +// TestCreateUser_PasswordEncryption 测试密码是否正确加密 +func TestCreateUser_PasswordEncryption(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewCreateUserLogic(ctx, svcCtx) + + req := &types.CreateUserRequest{ + Username: "testuser", + Email: "test@example.com", + Password: "password123", + } + + // 执行测试 + resp, err := logic.CreateUser(req) + require.NoError(t, err) + require.NotNil(t, resp) + + // 查询数据库验证密码已加密(不应该存储明文) + user, err := model.FindOne(context.Background(), svcCtx.DB, resp.Id) + require.NoError(t, err) + assert.NotEqual(t, "password123", user.Password) // 密码应该已被加密 +} + +// TestCreateUser_MultipleUsers 测试创建多个用户 +func TestCreateUser_MultipleUsers(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + ctx := context.Background() + + // 创建多个用户 + users := []struct { + username string + email string + }{ + {"user1", "user1@example.com"}, + {"user2", "user2@example.com"}, + {"user3", "user3@example.com"}, + } + + for _, u := range users { + logic := NewCreateUserLogic(ctx, svcCtx) + + req := &types.CreateUserRequest{ + Username: u.username, + Email: u.email, + Password: "password123", + } + + resp, err := logic.CreateUser(req) + + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, u.username, resp.Username) + assert.Equal(t, u.email, resp.Email) + } +} + +// BenchmarkCreateUser 性能测试 +func BenchmarkCreateUser(b *testing.B) { + svcCtx, _ := setupUserTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + ctx := context.Background() + req := &types.CreateUserRequest{ + Username: "testuser", + Email: "test@example.com", + Password: "password123", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // 使用不同的邮箱避免重复 + req.Email = "test@example.com" + logic := NewCreateUserLogic(ctx, svcCtx) + _, _ = logic.CreateUser(req) + } +} diff --git a/backend/internal/logic/user/deleteuserlogic_test.go b/backend/internal/logic/user/deleteuserlogic_test.go new file mode 100644 index 0000000..5d8796a --- /dev/null +++ b/backend/internal/logic/user/deleteuserlogic_test.go @@ -0,0 +1,332 @@ +package user + +import ( + "context" + "testing" + "time" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.DeleteUserRequest{} + +// TestDeleteUser_Success 测试成功删除用户 +func TestDeleteUser_Success(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + userId := user.Id + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewDeleteUserLogic(ctx, svcCtx) + + // 准备删除请求 + req := &types.DeleteUserRequest{ + Id: userId, + } + + // 执行测试 + resp, err := logic.DeleteUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 200, resp.Code) + assert.Equal(t, "删除成功", resp.Message) + assert.True(t, resp.Success) + + // 验证用户已被删除 + _, err = model.FindOne(context.Background(), svcCtx.DB, userId) + require.Error(t, err) + assert.Equal(t, model.ErrNotFound, err) +} + +// TestDeleteUser_NotFound 测试删除不存在的用户 +func TestDeleteUser_NotFound(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewDeleteUserLogic(ctx, svcCtx) + + // 使用不存在的用户ID + req := &types.DeleteUserRequest{ + Id: 99999, + } + + // 执行测试 + resp, err := logic.DeleteUser(req) + + // 验证结果 - 应该返回响应但用户不存在 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 404, resp.Code) + assert.Equal(t, "用户不存在", resp.Message) + assert.False(t, resp.Success) +} + +// TestDeleteUser_NegativeId 测试使用负数ID删除 +func TestDeleteUser_NegativeId(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewDeleteUserLogic(ctx, svcCtx) + + // 使用负数ID + req := &types.DeleteUserRequest{ + Id: -1, + } + + // 执行测试 + resp, err := logic.DeleteUser(req) + + // 验证结果 - 应该返回响应但用户不存在 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 404, resp.Code) + assert.Equal(t, "用户不存在", resp.Message) +} + +// TestDeleteUser_ZeroId 测试使用零ID删除 +func TestDeleteUser_ZeroId(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewDeleteUserLogic(ctx, svcCtx) + + // 使用零ID + req := &types.DeleteUserRequest{ + Id: 0, + } + + // 执行测试 + resp, err := logic.DeleteUser(req) + + // 验证结果 - 应该返回响应但用户不存在 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 404, resp.Code) + assert.Equal(t, "用户不存在", resp.Message) +} + +// TestDeleteUser_ThenRecreate 测试删除后重建用户 +func TestDeleteUser_ThenRecreate(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + oldUserId := user.Id + + // 创建上下文 + ctx := context.Background() + + // 删除用户 + logic1 := NewDeleteUserLogic(ctx, svcCtx) + req1 := &types.DeleteUserRequest{ + Id: oldUserId, + } + + _, err = logic1.DeleteUser(req1) + require.NoError(t, err) + + // 重建用户(使用相同的邮箱) + createLogic := NewCreateUserLogic(ctx, svcCtx) + createReq := &types.CreateUserRequest{ + Username: "testuser2", + Email: "test@example.com", + Password: "newpassword", + } + + newUser, err := createLogic.CreateUser(createReq) + + // 验证结果 - 新用户ID应该不同 + require.NoError(t, err) + require.NotNil(t, newUser) + assert.NotEqual(t, oldUserId, newUser.Id) +} + +// TestDeleteUser_DeletedTwice 测试重复删除同一个用户 +func TestDeleteUser_DeletedTwice(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + userId := user.Id + + // 创建上下文 + ctx := context.Background() + + // 第一次删除 + logic1 := NewDeleteUserLogic(ctx, svcCtx) + req := &types.DeleteUserRequest{ + Id: userId, + } + + resp1, err := logic1.DeleteUser(req) + require.NoError(t, err) + assert.Equal(t, 200, resp1.Code) + + // 第二次删除(应该返回404) + logic2 := NewDeleteUserLogic(ctx, svcCtx) + resp2, err := logic2.DeleteUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp2) + assert.Equal(t, 404, resp2.Code) + assert.Equal(t, "用户不存在", resp2.Message) +} + +// TestDeleteUser_WithProfile 测试删除带个人资料的用户 +func TestDeleteUser_WithProfile(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + userId := user.Id + + // 创建个人资料 + profile := &model.Profile{ + UserId: userId, + Avatar: "http://example.com/avatar.jpg", + Bio: "Test bio", + CreatedAt: now, + UpdatedAt: now, + } + + err = svcCtx.DB.Create(profile).Error + require.NoError(t, err) + + // 删除用户 + ctx := context.Background() + logic := NewDeleteUserLogic(ctx, svcCtx) + + req := &types.DeleteUserRequest{ + Id: userId, + } + + resp, err := logic.DeleteUser(req) + + // 验证结果 + require.NoError(t, err) + assert.Equal(t, 200, resp.Code) + + // 验证用户和资料都已删除(或用户被删除后,profile变成孤数据) + _, err = model.FindOne(context.Background(), svcCtx.DB, userId) + assert.Error(t, err) + assert.Equal(t, model.ErrNotFound, err) +} + +// BenchmarkDeleteUser 性能测试 +func BenchmarkDeleteUser(b *testing.B) { + svcCtx, _ := setupUserTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // 为每次迭代创建用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + if err != nil { + b.Fatal(err) + } + + logic := NewDeleteUserLogic(ctx, svcCtx) + req := &types.DeleteUserRequest{ + Id: user.Id, + } + + _, _ = logic.DeleteUser(req) + } +} diff --git a/backend/internal/logic/user/getuserlogic_test.go b/backend/internal/logic/user/getuserlogic_test.go new file mode 100644 index 0000000..6300b7b --- /dev/null +++ b/backend/internal/logic/user/getuserlogic_test.go @@ -0,0 +1,256 @@ +package user + +import ( + "context" + "testing" + "time" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.GetUserRequest{} + +// TestGetUser_Success 测试成功获取用户 +func TestGetUser_Success(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 准备获取用户请求数据 + req := &types.GetUserRequest{ + Id: user.Id, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) + assert.Equal(t, "13800138000", resp.Phone) + assert.Equal(t, 1, resp.Status) +} + +// TestGetUser_NotFound 测试用户不存在 +func TestGetUser_NotFound(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 使用不存在的用户ID + req := &types.GetUserRequest{ + Id: 99999, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestGetUser_NegativeId 测试使用负数ID +func TestGetUser_NegativeId(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 使用负数ID + req := &types.GetUserRequest{ + Id: -1, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestGetUser_ZeroId 测试使用零ID +func TestGetUser_ZeroId(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 使用零ID + req := &types.GetUserRequest{ + Id: 0, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestGetUser_UserWithEmptyPhone 测试获取没有手机号的用户 +func TestGetUser_UserWithEmptyPhone(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户,手机号为空 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 准备获取用户请求数据 + req := &types.GetUserRequest{ + Id: user.Id, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "", resp.Phone) +} + +// TestGetUser_DisabledUser 测试获取禁用状态的用户 +func TestGetUser_DisabledUser(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户,状态为禁用 + now := time.Now() + user := &model.User{ + Username: "disableduser", + Email: "disabled@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 2, // 禁用状态 + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewGetUserLogic(ctx, svcCtx) + + // 准备获取用户请求数据 + req := &types.GetUserRequest{ + Id: user.Id, + } + + // 执行测试 + resp, err := logic.GetUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 2, resp.Status) +} + +// BenchmarkGetUser 性能测试 +func BenchmarkGetUser(b *testing.B) { + svcCtx, _ := setupUserTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + if err != nil { + b.Fatal(err) + } + + ctx := context.Background() + req := &types.GetUserRequest{ + Id: user.Id, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + logic := NewGetUserLogic(ctx, svcCtx) + _, _ = logic.GetUser(req) + } +} diff --git a/backend/internal/logic/user/updateuserlogic_test.go b/backend/internal/logic/user/updateuserlogic_test.go new file mode 100644 index 0000000..659e0df --- /dev/null +++ b/backend/internal/logic/user/updateuserlogic_test.go @@ -0,0 +1,405 @@ +package user + +import ( + "context" + "testing" + "time" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.UpdateUserRequest{} + +// TestUpdateUser_Success 测试成功更新用户 +func TestUpdateUser_Success(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "olduser", + Email: "old@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateUserRequest{ + Id: user.Id, + Username: "newuser", + Email: "new@example.com", + Phone: "13900139000", + Status: 2, + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "newuser", resp.Username) + assert.Equal(t, "new@example.com", resp.Email) + assert.Equal(t, "13900139000", resp.Phone) + assert.Equal(t, 2, resp.Status) +} + +// TestUpdateUser_OnlyUsername 测试只更新用户名 +func TestUpdateUser_OnlyUsername(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "olduser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 准备更新请求数据 - 只更新用户名 + req := &types.UpdateUserRequest{ + Id: user.Id, + Username: "newuser", + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "newuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) // 保持不变 + assert.Equal(t, "13800138000", resp.Phone) // 保持不变 +} + +// TestUpdateUser_OnlyEmail 测试只更新邮箱 +func TestUpdateUser_OnlyEmail(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "old@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 准备更新请求数据 - 只更新邮箱 + req := &types.UpdateUserRequest{ + Id: user.Id, + Email: "new@example.com", + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "new@example.com", resp.Email) + assert.Equal(t, "testuser", resp.Username) // 保持不变 +} + +// TestUpdateUser_DuplicateEmail 测试使用重复的邮箱 +func TestUpdateUser_DuplicateEmail(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建两个测试用户 + now := time.Now() + user1 := &model.User{ + Username: "user1", + Email: "user1@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + user2 := &model.User{ + Username: "user2", + Email: "user2@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user1).Error + require.NoError(t, err) + err = svcCtx.DB.Create(user2).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 尝试将 user1 的邮箱更新为 user2 的邮箱 + req := &types.UpdateUserRequest{ + Id: user1.Id, + Email: "user2@example.com", + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "邮箱已被使用") +} + +// TestUpdateUser_SameEmail 测试使用相同的邮箱(应该允许) +func TestUpdateUser_SameEmail(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 使用相同的邮箱 + req := &types.UpdateUserRequest{ + Id: user.Id, + Email: "test@example.com", + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 - 应该成功 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "test@example.com", resp.Email) +} + +// TestUpdateUser_NotFound 测试更新不存在的用户 +func TestUpdateUser_NotFound(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 使用不存在的用户ID + req := &types.UpdateUserRequest{ + Id: 99999, + Username: "newuser", + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 - 应该返回错误 + require.Error(t, err) + require.Nil(t, resp) + assert.Contains(t, err.Error(), "用户不存在") +} + +// TestUpdateUser_EmptyRequest 测试空请求 +func TestUpdateUser_EmptyRequest(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 空请求 + req := &types.UpdateUserRequest{ + Id: user.Id, + } + + // 执行测试 - 应该成功,只是不做任何更新 + resp, err := logic.UpdateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) +} + +// TestUpdateUser_ChangeStatus 测试修改用户状态 +func TestUpdateUser_ChangeStatus(t *testing.T) { + svcCtx, cleanup := setupUserTestDB(t) + defer cleanup() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + require.NoError(t, err) + + // 创建上下文 + ctx := context.Background() + + // 创建 Logic 实例 + logic := NewUpdateUserLogic(ctx, svcCtx) + + // 禁用用户 + req := &types.UpdateUserRequest{ + Id: user.Id, + Status: 2, + } + + // 执行测试 + resp, err := logic.UpdateUser(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 2, resp.Status) + + // 再次启用用户 + logic2 := NewUpdateUserLogic(ctx, svcCtx) + req2 := &types.UpdateUserRequest{ + Id: user.Id, + Status: 1, + } + + resp2, err := logic2.UpdateUser(req2) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp2) + assert.Equal(t, 1, resp2.Status) +} + +// BenchmarkUpdateUser 性能测试 +func BenchmarkUpdateUser(b *testing.B) { + svcCtx, _ := setupUserTestDB(&testing.T{}) + defer func() { + sqlDB, _ := svcCtx.DB.DB() + if sqlDB != nil { + sqlDB.Close() + } + }() + + // 创建测试用户 + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := svcCtx.DB.Create(user).Error + if err != nil { + b.Fatal(err) + } + + ctx := context.Background() + req := &types.UpdateUserRequest{ + Id: user.Id, + Username: "newuser", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + logic := NewUpdateUserLogic(ctx, svcCtx) + _, _ = logic.UpdateUser(req) + } +} diff --git a/backend/model/profile_model_test.go b/backend/model/profile_model_test.go new file mode 100644 index 0000000..cd2fa40 --- /dev/null +++ b/backend/model/profile_model_test.go @@ -0,0 +1,408 @@ +package model + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// setupProfileModelTestDB 创建测试数据库(使用MySQL) +func setupProfileModelTestDB(t *testing.T) *gorm.DB { + t.Helper() + + // 使用 MySQL 进行测试 + dsn := "root:dev123456@tcp(219.159.132.177:17173)/base?charset=utf8mb4&parseTime=true&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + require.NoError(t, err) + + err = db.AutoMigrate(&User{}, &Profile{}) + require.NoError(t, err) + + // 清理所有测试数据(TRUNCATE) + db.Exec("SET FOREIGN_KEY_CHECKS = 0") + db.Exec("TRUNCATE TABLE profile") + db.Exec("TRUNCATE TABLE user") + db.Exec("SET FOREIGN_KEY_CHECKS = 1") + + return db +} + +// createTestUserProfile 创建测试用户和关联的个人资料 +func createTestUserProfile(t *testing.T, db *gorm.DB) (int64, int64) { + t.Helper() + + now := time.Now() + + // 创建用户 + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + // 创建个人资料 + profile := &Profile{ + UserId: user.Id, + Avatar: "http://example.com/avatar.jpg", + Bio: "Test bio", + CreatedAt: now, + UpdatedAt: now, + } + + err = db.Create(profile).Error + require.NoError(t, err) + + return user.Id, profile.Id +} + +// TestInsertProfile 测试插入个人资料 +func TestInsertProfile(t *testing.T) { + db := setupProfileModelTestDB(t) + + ctx := context.Background() + + // 先创建用户 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + // 创建个人资料 + profile := &Profile{ + UserId: user.Id, + Avatar: "http://example.com/avatar.jpg", + Bio: "Test bio", + } + + id, err := InsertProfile(ctx, db, profile) + + require.NoError(t, err) + assert.Greater(t, id, int64(0)) + assert.Equal(t, id, profile.Id) + + // 验证数据已保存 + saved, err := FindOneProfile(ctx, db, id) + require.NoError(t, err) + assert.Equal(t, "http://example.com/avatar.jpg", saved.Avatar) + assert.Equal(t, "Test bio", saved.Bio) +} + +// TestFindOneProfile 测试根据ID查询个人资料 +func TestFindOneProfile(t *testing.T) { + db := setupProfileModelTestDB(t) + + _, profileId := createTestUserProfile(t, db) + ctx := context.Background() + + // 查询个人资料 + found, err := FindOneProfile(ctx, db, profileId) + + require.NoError(t, err) + require.NotNil(t, found) + assert.Equal(t, profileId, found.Id) + assert.Equal(t, "http://example.com/avatar.jpg", found.Avatar) + assert.Equal(t, "Test bio", found.Bio) +} + +// TestFindOneProfile_NotFound 测试查询不存在的个人资料 +func TestFindOneProfile_NotFound(t *testing.T) { + db := setupProfileModelTestDB(t) + + ctx := context.Background() + + found, err := FindOneProfile(ctx, db, 99999) + + require.Error(t, err) + require.Nil(t, found) + assert.Equal(t, ErrNotFound, err) +} + +// TestFindProfileByUserId 测试根据用户ID查询个人资料 +func TestFindProfileByUserId(t *testing.T) { + db := setupProfileModelTestDB(t) + + userId, _ := createTestUserProfile(t, db) + ctx := context.Background() + + // 根据用户ID查询 + found, err := FindProfileByUserId(ctx, db, userId) + + require.NoError(t, err) + require.NotNil(t, found) + assert.Equal(t, userId, found.UserId) + assert.Equal(t, "http://example.com/avatar.jpg", found.Avatar) +} + +// TestFindProfileByUserId_NotFound 测试查询不存在的用户个人资料 +func TestFindProfileByUserId_NotFound(t *testing.T) { + db := setupProfileModelTestDB(t) + + ctx := context.Background() + + found, err := FindProfileByUserId(ctx, db, 99999) + + require.Error(t, err) + require.Nil(t, found) + assert.Equal(t, ErrNotFound, err) +} + +// TestUpdateProfile 测试更新个人资料 +func TestUpdateProfile(t *testing.T) { + db := setupProfileModelTestDB(t) + + _, profileId := createTestUserProfile(t, db) + ctx := context.Background() + + // 获取个人资料 + profile, err := FindOneProfile(ctx, db, profileId) + require.NoError(t, err) + + // 修改数据 + profile.Avatar = "http://example.com/newavatar.jpg" + profile.Bio = "Updated bio" + + // 更新 + err = UpdateProfile(ctx, db, profile) + + require.NoError(t, err) + + // 验证更新结果 + updated, err := FindOneProfile(ctx, db, profileId) + require.NoError(t, err) + assert.Equal(t, "http://example.com/newavatar.jpg", updated.Avatar) + assert.Equal(t, "Updated bio", updated.Bio) +} + +// TestDeleteProfile 测试删除个人资料 +func TestDeleteProfile(t *testing.T) { + db := setupProfileModelTestDB(t) + + _, profileId := createTestUserProfile(t, db) + ctx := context.Background() + + // 删除个人资料 + err := DeleteProfile(ctx, db, profileId) + + require.NoError(t, err) + + // 验证个人资料已被删除 + _, err = FindOneProfile(ctx, db, profileId) + require.Error(t, err) + assert.Equal(t, ErrNotFound, err) +} + +// TestProfile_UserProfileRelation 测试用户与个人资料的关联 +func TestProfile_UserProfileRelation(t *testing.T) { + db := setupProfileModelTestDB(t) + ctx := context.Background() + + // 创建多个用户 + now := time.Now() + users := []User{ + {Username: "user1", Email: "user1@example.com", Password: "pass", Status: 1, CreatedAt: now, UpdatedAt: now}, + {Username: "user2", Email: "user2@example.com", Password: "pass", Status: 1, CreatedAt: now, UpdatedAt: now}, + {Username: "user3", Email: "user3@example.com", Password: "pass", Status: 1, CreatedAt: now, UpdatedAt: now}, + } + + for _, u := range users { + err := db.Create(&u).Error + require.NoError(t, err) + } + + // 只为 user1 和 user2 创建个人资料 + profile1 := &Profile{UserId: users[0].Id, Avatar: "avatar1.jpg", Bio: "Bio 1", CreatedAt: now, UpdatedAt: now} + profile2 := &Profile{UserId: users[1].Id, Avatar: "avatar2.jpg", Bio: "Bio 2", CreatedAt: now, UpdatedAt: now} + + err := db.Create(profile1).Error + require.NoError(t, err) + err = db.Create(profile2).Error + require.NoError(t, err) + + // 验证 user1 有个人资料 + found1, err := FindProfileByUserId(ctx, db, users[0].Id) + require.NoError(t, err) + assert.Equal(t, "avatar1.jpg", found1.Avatar) + + // 验证 user2 有个人资料 + found2, err := FindProfileByUserId(ctx, db, users[1].Id) + require.NoError(t, err) + assert.Equal(t, "avatar2.jpg", found2.Avatar) + + // 验证 user3 没有个人资料 + found3, err := FindProfileByUserId(ctx, db, users[2].Id) + require.Error(t, err) + require.Nil(t, found3) +} + +// TestProfile_UniqueUserId 测试用户ID唯一性约束 +func TestProfile_UniqueUserId(t *testing.T) { + db := setupProfileModelTestDB(t) + ctx := context.Background() + + // 创建用户 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + // 创建第一个个人资料 + profile1 := &Profile{ + UserId: user.Id, + Avatar: "avatar1.jpg", + Bio: "Bio 1", + } + + id, err := InsertProfile(ctx, db, profile1) + require.NoError(t, err) + assert.Greater(t, id, int64(0)) + + // 尝试为同一用户创建第二个个人资料(应该失败) + profile2 := &Profile{ + UserId: user.Id, + Avatar: "avatar2.jpg", + Bio: "Bio 2", + } + + _, err = InsertProfile(ctx, db, profile2) + + // 验证结果 - 应该返回错误(违反唯一性约束) + require.Error(t, err) +} + +// TestProfile_EmptyFields 测试空字段 +func TestProfile_EmptyFields(t *testing.T) { + db := setupProfileModelTestDB(t) + ctx := context.Background() + + // 创建用户 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + // 创建空个人资料 + profile := &Profile{ + UserId: user.Id, + Avatar: "", + Bio: "", + } + + id, err := InsertProfile(ctx, db, profile) + + require.NoError(t, err) + assert.Greater(t, id, int64(0)) + + // 验证保存成功 + saved, err := FindOneProfile(ctx, db, id) + require.NoError(t, err) + assert.Equal(t, "", saved.Avatar) + assert.Equal(t, "", saved.Bio) +} + +// BenchmarkInsertProfile 性能测试 +func BenchmarkInsertProfile(b *testing.B) { + db := setupProfileModelTestDB(&testing.T{}) + ctx := context.Background() + + // 创建测试用户 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + profile := &Profile{ + UserId: user.Id, + Avatar: "http://example.com/avatar.jpg", + Bio: "Test bio", + } + _, _ = InsertProfile(ctx, db, profile) + } +} + +// BenchmarkFindProfileByUserId 性能测试 +func BenchmarkFindProfileByUserId(b *testing.B) { + db := setupProfileModelTestDB(&testing.T{}) + ctx := context.Background() + + // 创建测试用户和个人资料 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + if err != nil { + b.Fatal(err) + } + + profile := &Profile{ + UserId: user.Id, + Avatar: "http://example.com/avatar.jpg", + Bio: "Test bio", + CreatedAt: now, + UpdatedAt: now, + } + + err = db.Create(profile).Error + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindProfileByUserId(ctx, db, user.Id) + } +} diff --git a/backend/model/user_model_test.go b/backend/model/user_model_test.go new file mode 100644 index 0000000..1a5c638 --- /dev/null +++ b/backend/model/user_model_test.go @@ -0,0 +1,368 @@ +package model + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// setupUserModelTestDB 创建测试数据库(使用MySQL) +func setupUserModelTestDB(t *testing.T) *gorm.DB { + t.Helper() + + // 使用 MySQL 进行测试 + dsn := "root:dev123456@tcp(219.159.132.177:17173)/base?charset=utf8mb4&parseTime=true&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + require.NoError(t, err) + + err = db.AutoMigrate(&User{}, &Profile{}) + require.NoError(t, err) + + // 清理所有测试数据(TRUNCATE) + db.Exec("SET FOREIGN_KEY_CHECKS = 0") + db.Exec("TRUNCATE TABLE profile") + db.Exec("TRUNCATE TABLE user") + db.Exec("SET FOREIGN_KEY_CHECKS = 1") + + return db +} + +// createTestUser 创建测试用户 +func createTestUserModel(t *testing.T, db *gorm.DB) *User { + t.Helper() + + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + return user +} + +// TestInsertUser 测试插入用户 +func TestInsertUser(t *testing.T) { + db := setupUserModelTestDB(t) + + ctx := context.Background() + user := &User{ + Username: "newuser", + Email: "new@example.com", + Password: "encryptedpassword", + Phone: "13900139000", + Status: 1, + } + + id, err := Insert(ctx, db, user) + + require.NoError(t, err) + assert.Greater(t, id, int64(0)) + assert.Equal(t, id, user.Id) + + // 验证数据已保存 + saved, err := FindOne(ctx, db, id) + require.NoError(t, err) + assert.Equal(t, "newuser", saved.Username) + assert.Equal(t, "new@example.com", saved.Email) +} + +// TestFindOneUser 测试根据ID查询用户 +func TestFindOneUser(t *testing.T) { + db := setupUserModelTestDB(t) + + // 创建测试用户 + user := createTestUserModel(t, db) + ctx := context.Background() + + // 查询用户 + found, err := FindOne(ctx, db, user.Id) + + require.NoError(t, err) + require.NotNil(t, found) + assert.Equal(t, user.Id, found.Id) + assert.Equal(t, "testuser", found.Username) + assert.Equal(t, "test@example.com", found.Email) +} + +// TestFindOneUser_NotFound 测试查询不存在的用户 +func TestFindOneUser_NotFound(t *testing.T) { + db := setupUserModelTestDB(t) + + ctx := context.Background() + + found, err := FindOne(ctx, db, 99999) + + require.Error(t, err) + require.Nil(t, found) + assert.Equal(t, ErrNotFound, err) +} + +// TestFindOneByEmail 测试根据邮箱查询用户 +func TestFindOneByEmail(t *testing.T) { + db := setupUserModelTestDB(t) + + // 创建测试用户 + user := createTestUserModel(t, db) + ctx := context.Background() + + // 根据邮箱查询 + found, err := FindOneByEmail(ctx, db, "test@example.com") + + require.NoError(t, err) + require.NotNil(t, found) + assert.Equal(t, user.Id, found.Id) + assert.Equal(t, "testuser", found.Username) +} + +// TestFindOneByEmail_NotFound 测试查询不存在的邮箱 +func TestFindOneByEmail_NotFound(t *testing.T) { + db := setupUserModelTestDB(t) + + ctx := context.Background() + + found, err := FindOneByEmail(ctx, db, "nonexistent@example.com") + + require.Error(t, err) + require.Nil(t, found) + assert.Equal(t, ErrNotFound, err) +} + +// TestUpdateUser 测试更新用户 +func TestUpdateUser(t *testing.T) { + db := setupUserModelTestDB(t) + + // 创建测试用户 + user := createTestUserModel(t, db) + ctx := context.Background() + + // 修改用户数据 + user.Username = "updateduser" + user.Phone = "13900139000" + + // 更新 + err := Update(ctx, db, user) + + require.NoError(t, err) + + // 验证更新结果 + updated, err := FindOne(ctx, db, user.Id) + require.NoError(t, err) + assert.Equal(t, "updateduser", updated.Username) + assert.Equal(t, "13900139000", updated.Phone) +} + +// TestDeleteUser 测试删除用户 +func TestDeleteUser(t *testing.T) { + db := setupUserModelTestDB(t) + + // 创建测试用户 + user := createTestUserModel(t, db) + userId := user.Id + ctx := context.Background() + + // 删除用户 + err := Delete(ctx, db, userId) + + require.NoError(t, err) + + // 验证用户已被删除 + _, err = FindOne(ctx, db, userId) + require.Error(t, err) + assert.Equal(t, ErrNotFound, err) +} + +// TestFindList 测试查询用户列表 +func TestFindList(t *testing.T) { + db := setupUserModelTestDB(t) + ctx := context.Background() + + // 创建多个测试用户 + now := time.Now() + users := []User{ + {Username: "user1", Email: "user1@example.com", Password: "pass", Phone: "13800000001", Status: 1, CreatedAt: now, UpdatedAt: now}, + {Username: "user2", Email: "user2@example.com", Password: "pass", Phone: "13800000002", Status: 1, CreatedAt: now, UpdatedAt: now}, + {Username: "user3", Email: "user3@example.com", Password: "pass", Phone: "13800000003", Status: 2, CreatedAt: now, UpdatedAt: now}, + {Username: "user4", Email: "user4@example.com", Password: "pass", Phone: "13800000004", Status: 2, CreatedAt: now, UpdatedAt: now}, + {Username: "user5", Email: "user5@example.com", Password: "pass", Phone: "13800000005", Status: 1, CreatedAt: now, UpdatedAt: now}, + } + + for _, u := range users { + err := db.Create(&u).Error + require.NoError(t, err) + } + + // 测试分页查询 + list, total, err := FindList(ctx, db, 1, 2, "", 0) + + require.NoError(t, err) + assert.Equal(t, int64(5), total) + assert.Len(t, list, 2) + + // 测试状态筛选 + list, total, err = FindList(ctx, db, 1, 10, "", 1) + + require.NoError(t, err) + assert.Equal(t, int64(3), total) + + // 测试关键词搜索 + list, total, err = FindList(ctx, db, 1, 10, "user1", 0) + + require.NoError(t, err) + assert.Equal(t, int64(1), total) + assert.Equal(t, "user1", list[0].Username) + + // 测试第二页 + list, total, err = FindList(ctx, db, 2, 2, "", 0) + + require.NoError(t, err) + assert.Equal(t, int64(5), total) + assert.Len(t, list, 2) + assert.Equal(t, "user3", list[0].Username) +} + +// TestFindList_EmptyResult 测试查询空列表 +func TestFindList_EmptyResult(t *testing.T) { + db := setupUserModelTestDB(t) + ctx := context.Background() + + list, total, err := FindList(ctx, db, 1, 10, "", 0) + + require.NoError(t, err) + assert.Equal(t, int64(0), total) + assert.Empty(t, list) +} + +// TestFindList_WithPhoneSearch 测试通过手机号搜索 +func TestFindList_WithPhoneSearch(t *testing.T) { + db := setupUserModelTestDB(t) + ctx := context.Background() + + // 创建测试用户 + now := time.Now() + user1 := &User{Username: "user1", Email: "user1@example.com", Password: "pass", Phone: "13812345678", Status: 1, CreatedAt: now, UpdatedAt: now} + user2 := &User{Username: "user2", Email: "user2@example.com", Password: "pass", Phone: "13887654321", Status: 1, CreatedAt: now, UpdatedAt: now} + + err := db.Create(user1).Error + require.NoError(t, err) + err = db.Create(user2).Error + require.NoError(t, err) + + // 搜索手机号 + list, total, err := FindList(ctx, db, 1, 10, "138123", 0) + + require.NoError(t, err) + assert.Equal(t, int64(1), total) + assert.Equal(t, "user1", list[0].Username) +} + +// TestFindList_WithEmailSearch 测试通过邮箱搜索 +func TestFindList_WithEmailSearch(t *testing.T) { + db := setupUserModelTestDB(t) + ctx := context.Background() + + // 创建测试用户 + now := time.Now() + user1 := &User{Username: "user1", Email: "test@company.com", Password: "pass", Phone: "13800000001", Status: 1, CreatedAt: now, UpdatedAt: now} + user2 := &User{Username: "user2", Email: "other@company.com", Password: "pass", Phone: "13800000002", Status: 1, CreatedAt: now, UpdatedAt: now} + + err := db.Create(user1).Error + require.NoError(t, err) + err = db.Create(user2).Error + require.NoError(t, err) + + // 搜索邮箱 + _, total, err := FindList(ctx, db, 1, 10, "@company.com", 0) + + require.NoError(t, err) + assert.Equal(t, int64(2), total) +} + +// BenchmarkInsertUser 性能测试 +func BenchmarkInsertUser(b *testing.B) { + db := setupUserModelTestDB(&testing.T{}) + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + _, _ = Insert(ctx, db, user) + } +} + +// BenchmarkFindOneUser 性能测试 +func BenchmarkFindOneUser(b *testing.B) { + db := setupUserModelTestDB(&testing.T{}) + ctx := context.Background() + + // 创建测试用户 + now := time.Now() + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = FindOne(ctx, db, user.Id) + } +} + +// BenchmarkFindList 性能测试 +func BenchmarkFindList(b *testing.B) { + db := setupUserModelTestDB(&testing.T{}) + ctx := context.Background() + + // 创建100个测试用户 + now := time.Now() + for i := 0; i < 100; i++ { + user := &User{ + Username: "testuser", + Email: "test@example.com", + Password: "encryptedpassword", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + err := db.Create(user).Error + if err != nil { + b.Fatal(err) + } + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, _ = FindList(ctx, db, 1, 10, "", 0) + } +} diff --git a/backend/run_logic_tests.sh b/backend/run_logic_tests.sh new file mode 100644 index 0000000..25309b7 --- /dev/null +++ b/backend/run_logic_tests.sh @@ -0,0 +1,166 @@ +package profile + +import ( + "context" + "testing" + "time" + + "github.com/youruser/base/internal/svc" + "github.com/youruser/base/internal/types" + "github.com/youruser/base/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Ensure imports are used +var _ = svc.ServiceContext{} +var _ = types.GetProfileResponse{} + +// setupTestDB 创建测试数据库并返回 ServiceContext(使用MySQL) +func setupTestDB(t *testing.T) (*svc.ServiceContext, func()) { + t.Helper() + + // 使用 MySQL 进行测试 + dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=true&loc=Local" + db, err := gorm.Open(dsn, &gorm.Config{}) + require.NoError(t, err) + + // 自动迁移表 + err = db.AutoMigrate(&model.User{}, &model.Profile{}) + require.NoError(t, err) + + // 创建测试 ServiceContext + svcCtx := &svc.ServiceContext{ + DB: db, + } + + // 清理函数 + cleanup := func() { + sqlDB, _ := db.DB() + if sqlDB != nil { + sqlDB.Close() + } + } + + return svcCtx, cleanup +} + +// createTestUser 创建测试用户 +func createTestUser(t *testing.T, db *gorm.DB) *model.User { + t.Helper() + + now := time.Now() + user := &model.User{ + Username: "testuser", + Email: "test@example.com", + Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", + Phone: "13800138000", + Status: 1, + CreatedAt: now, + UpdatedAt: now, + } + + err := db.Create(user).Error + require.NoError(t, err) + + return user +} + +// TestGetProfile_Success 测试成功获取个人信息 +func TestGetProfile_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + createTestProfile(t, svcCtx.DB, user.Id) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewGetProfileLogic(ctx, svcCtx) + + // 执行测试 + resp, err := logic.GetProfile() + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "testuser", resp.Username) + assert.Equal(t, "test@example.com", resp.Email) + assert.Equal(t, "13800138000", resp.Phone) + assert.Equal(t, "http://example.com/avatar.jpg", resp.Avatar) + assert.Equal(t, "This is a test bio", resp.Bio) + assert.Equal(t, 1, resp.Status) +} + +// TestUpdateProfile_Success 测试成功更新个人资料 +func TestUpdateProfile_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户 + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewUpdateProfileLogic(ctx, svcCtx) + + // 准备更新请求数据 + req := &types.UpdateProfileRequest{ + Username: "updateduser", + Phone: "13900139000", + Avatar: "http://example.com/newavatar.jpg", + Bio: "Updated bio text", + } + + // 执行测试 + resp, err := logic.UpdateProfile(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, user.Id, resp.Id) + assert.Equal(t, "updateduser", resp.Username) + assert.Equal(t, "13900139000", resp.Phone) + assert.Equal(t, "http://example.com/newavatar.jpg", resp.Avatar) + assert.Equal(t, "Updated bio text", resp.Bio) +} + +// TestChangePassword_Success 测试成功修改密码 +func TestChangePassword_Success(t *testing.T) { + svcCtx, cleanup := setupTestDB(t) + defer cleanup() + + // 创建测试用户,密码为 "password" + user := createTestUser(t, svcCtx.DB) + + // 设置上下文,包含用户ID + ctx := context.WithValue(context.Background(), "userId", user.Id) + + // 创建 Logic 实例 + logic := NewChangePasswordLogic(ctx, svcCtx) + + // 准备修改密码请求数据 + req := &types.ChangePasswordRequest{ + OldPassword: "password", + NewPassword: "newpassword123", + } + + // 执行测试 + resp, err := logic.ChangePassword(req) + + // 验证结果 + require.NoError(t, err) + require.NotNil(t, resp) + assert.Equal(t, 200, resp.Code) + assert.Equal(t, "修改密码成功", resp.Message) +} +} diff --git a/backend/run_tests.bat b/backend/run_tests.bat new file mode 100644 index 0000000..69484fe --- /dev/null +++ b/backend/run_tests.bat @@ -0,0 +1,4 @@ +@echo off +set CGO_ENABLED=1 +go test -v ./internal/logic/... %* +pause diff --git a/backend/tests/user/test_user.sh b/backend/tests/user/test_user.sh index 7501b84..e2b288c 100644 --- a/backend/tests/user/test_user.sh +++ b/backend/tests/user/test_user.sh @@ -101,7 +101,7 @@ log_success "Verify success" log_step "5" "List query" LIST_RESULT=$(curl -s -X GET "${BASE_URL}/${MODULE}s?page=1&pageSize=10" -H "Authorization: Bearer ${AUTH_TOKEN}") -if echo "$LIST_RESULT" | grep -q '"id"'; then +if echo "$LIST_RESULT" | grep -q "\"id\"; then log_success "List success" else log_error "List failed"