Files
user-system/internal/service/boundary_test.go

357 lines
9.9 KiB
Go
Raw Normal View History

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
}