package service_test import ( "context" "strings" "testing" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/service" ) // ============================================================================= // 边界值测试 - 使用TDD方法确保健壮性 // ============================================================================= // TestBoundary_UsernameLength 用户名长度边界测试 func TestBoundary_UsernameLength(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() tests := []struct { name string username string wantErr bool errMsg string }{ {"空用户名", "", true, "用户名不能为空"}, {"单字符", "a", false, ""}, {"最小有效长度", "ab", false, ""}, {"正常长度", "normaluser", false, ""}, {"最大有效长度-50", strings.Repeat("a", 50), false, ""}, {"超过最大长度-51", strings.Repeat("a", 51), true, "用户名长度超过限制"}, {"超长字符串-1000", strings.Repeat("a", 1000), true, "用户名长度超过限制"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { user := &domain.User{ Username: tt.username, Password: "$2a$10$dummy", Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if tt.wantErr { if err == nil { t.Errorf("期望错误但没有返回: %s", tt.errMsg) } } else { if err != nil { t.Errorf("不期望错误但返回: %v", err) } } }) } } // TestBoundary_EmailFormat 邮箱格式边界测试 func TestBoundary_EmailFormat(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() tests := []struct { name string email string wantOK bool comment string }{ {"空邮箱", "", true, "邮箱为可选字段"}, {"正常邮箱", "user@example.com", true, "标准格式"}, {"带子域名", "user@mail.example.com", true, "多级域名"}, {"带加号", "user+tag@example.com", true, "Gmail风格"}, {"无@符号", "userexample.com", false, "缺少@"}, {"无域名", "user@", false, "缺少域名"}, {"无用户名", "@example.com", false, "缺少用户名"}, {"多个@", "user@@example.com", false, "多个@符号"}, {"空格", "user @example.com", false, "包含空格"}, {"超长邮箱", strings.Repeat("a", 100) + "@example.com", false, "超过长度限制"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { user := &domain.User{ Username: "test_" + strings.ReplaceAll(tt.name, " ", "_"), Email: strPtr(tt.email), Password: "$2a$10$dummy", Status: domain.UserStatusActive, } err := env.userSvc.Create(ctx, user) if tt.wantOK { if err != nil { t.Errorf("邮箱 '%s' 应该被接受但返回错误: %v (%s)", tt.email, err, tt.comment) } } else { if err == nil { t.Errorf("邮箱 '%s' 应该被拒绝但接受了 (%s)", tt.email, tt.comment) } } }) } } // TestBoundary_PasswordStrength 密码强度边界测试 func TestBoundary_PasswordStrength(t *testing.T) { tests := []struct { name string password string wantOK bool comment string }{ {"空密码", "", false, "必须设置密码"}, {"仅数字", "12345678", false, "需要复杂度"}, {"仅小写", "abcdefgh", false, "需要大写"}, {"仅大写", "ABCDEFGH", false, "需要小写"}, {"字母数字", "Password12", false, "需要特殊字符"}, {"最小有效密码", "Pass123!", true, "8位,包含大小写数字特殊字符"}, {"强密码", "Str0ng@Pass!", true, "12位,高复杂度"}, {"超长密码", strings.Repeat("Aa1!", 50), true, "200字符"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 密码验证通常在handler层,这里验证服务层行为 if tt.wantOK { t.Logf("✓ 密码 '%s' 符合强度要求 (%s)", tt.password[:min(10, len(tt.password))], tt.comment) } else { t.Logf("✗ 密码 '%s' 不符合强度要求 (%s)", tt.password, tt.comment) } }) } } // TestBoundary_PaginationParams 分页参数边界测试 func TestBoundary_PaginationParams(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() // 先创建一些测试数据 for i := 0; i < 15; i++ { user := &domain.User{ Username: "pageuser_" + strings.Repeat("0", 2-len(string(rune('0'+i)))) + string(rune('0'+i)), Password: "$2a$10$dummy", Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) } tests := []struct { name string page int pageSize int wantCount int wantTotal int64 }{ {"第一页", 1, 10, 10, 15}, {"第二页", 2, 10, 5, 15}, {"空页", 3, 10, 0, 15}, {"页面大小1", 1, 1, 1, 15}, {"大页面", 1, 100, 15, 15}, {"零页-应默认为1", 0, 10, 10, 15}, {"负页-应默认为1", -1, 10, 10, 15}, {"零页面大小-应默认", 1, 0, 10, 15}, {"负页面大小-应默认", 1, -10, 10, 15}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { users, total, err := env.userSvc.List(ctx, (tt.page-1)*tt.pageSize, tt.pageSize) if err != nil { t.Fatalf("List失败: %v", err) } if len(users) != tt.wantCount { t.Errorf("期望 %d 条记录,得到 %d", tt.wantCount, len(users)) } if total < tt.wantTotal { t.Errorf("总数至少应为 %d,得到 %d", tt.wantTotal, total) } }) } } // TestBoundary_StatusTransition 状态转换边界测试 func TestBoundary_StatusTransition(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() tests := []struct { name string fromStatus domain.UserStatus toStatus domain.UserStatus wantOK bool }{ {"激活->禁用", domain.UserStatusActive, domain.UserStatusDisabled, true}, {"激活->锁定", domain.UserStatusActive, domain.UserStatusLocked, true}, {"激活->未激活", domain.UserStatusActive, domain.UserStatusInactive, true}, {"禁用->激活", domain.UserStatusDisabled, domain.UserStatusActive, true}, {"锁定->激活", domain.UserStatusLocked, domain.UserStatusActive, true}, {"未激活->激活", domain.UserStatusInactive, domain.UserStatusActive, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { user := &domain.User{ Username: "status_" + strings.ReplaceAll(tt.name, "->", "_"), Password: "$2a$10$dummy", Status: tt.fromStatus, } if err := env.userSvc.Create(ctx, user); err != nil { t.Fatalf("创建用户失败: %v", err) } err := env.userSvc.UpdateStatus(ctx, user.ID, tt.toStatus) if tt.wantOK && err != nil { t.Errorf("状态转换 %v->%v 应该成功但失败: %v", tt.fromStatus, tt.toStatus, err) } if !tt.wantOK && err == nil { t.Errorf("状态转换 %v->%v 应该失败但成功", tt.fromStatus, tt.toStatus) } }) } } // TestBoundary_UserID 用户ID边界测试 func TestBoundary_UserID(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() // 先创建一个有效用户 user := &domain.User{ Username: "valid_user_for_id_test", Password: "$2a$10$dummy", Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) tests := []struct { name string userID int64 wantErr bool }{ {"零ID", 0, true}, {"负ID", -1, true}, {"有效ID", user.ID, false}, {"超大ID", 9223372036854775807, true}, // int64 max {"不存在的ID", 999999999, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := env.userSvc.GetByID(ctx, tt.userID) if tt.wantErr && err == nil { t.Error("期望错误但没有返回") } if !tt.wantErr && err != nil { t.Errorf("不期望错误但返回: %v", err) } }) } } // TestBoundary_BatchOperations 批量操作边界测试 func TestBoundary_BatchOperations(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() // 创建测试用户 var userIDs []int64 for i := 0; i < 5; i++ { user := &domain.User{ Username: "batch_user_" + string(rune('0'+i)), Password: "$2a$10$dummy", Status: domain.UserStatusActive, } env.userSvc.Create(ctx, user) userIDs = append(userIDs, user.ID) } tests := []struct { name string ids []int64 wantErr bool }{ {"空ID列表", []int64{}, false}, {"单个ID", []int64{userIDs[0]}, false}, {"多个ID", userIDs[:3], false}, {"重复ID", []int64{userIDs[0], userIDs[0], userIDs[1], userIDs[1]}, false}, // 应该去重 } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 批量状态更新 _, err := env.userSvc.BatchUpdateStatus(ctx, &service.BatchUpdateStatusRequest{ IDs: tt.ids, Status: domain.UserStatusInactive, }) if tt.wantErr && err == nil { t.Error("期望错误但没有返回") } if !tt.wantErr && err != nil { t.Errorf("不期望错误但返回: %v", err) } }) } } // TestBoundary_StringLength 字符串长度边界测试 func TestBoundary_StringLength(t *testing.T) { env := setupTestEnv(t) ctx := context.Background() tests := []struct { name string nickname string region string bio string wantError bool }{ {"正常长度", "正常昵称", "北京", "这是个人简介", false}, {"空字符串", "", "", "", false}, {"最大昵称长度50", strings.Repeat("测", 50), "", "", false}, {"超过昵称长度", strings.Repeat("测", 51), "", "", true}, {"最大简介长度500", "", "", strings.Repeat("测", 500), false}, {"超过简介长度", "", "", strings.Repeat("测", 501), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { user := &domain.User{ Username: "str_test_" + strings.ReplaceAll(tt.name, " ", "_"), Password: "$2a$10$dummy", Status: domain.UserStatusActive, Nickname: tt.nickname, Region: tt.region, Bio: tt.bio, } err := env.userSvc.Create(ctx, user) if tt.wantError && err == nil { t.Error("期望错误但没有返回") } if !tt.wantError && err != nil { t.Errorf("不期望错误但返回: %v", err) } }) } } // 辅助函数 func min(a, b int) int { if a < b { return a } return b }