在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
503 lines
12 KiB
Go
503 lines
12 KiB
Go
package unit
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/stretchr/testify/assert"
|
||
)
|
||
|
||
// TestUserValidation 测试用户模型验证
|
||
func TestUserValidation(t *testing.T) {
|
||
validate := validator.New()
|
||
|
||
t.Run("有效的创建用户请求", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "validuser",
|
||
Email: "valid@example.com",
|
||
Password: "password123",
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.NoError(t, err)
|
||
})
|
||
|
||
t.Run("用户名太短", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "ab", // 少于 3 个字符
|
||
Email: "valid@example.com",
|
||
Password: "password123",
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("用户名太长", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "a123456789012345678901234567890123456789012345678901", // 超过 50 个字符
|
||
Email: "valid@example.com",
|
||
Password: "password123",
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("无效的邮箱格式", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "validuser",
|
||
Email: "invalid-email",
|
||
Password: "password123",
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("密码太短", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "validuser",
|
||
Email: "valid@example.com",
|
||
Password: "short", // 少于 8 个字符
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("缺少必填字段", func(t *testing.T) {
|
||
req := &model.CreateUserRequest{
|
||
Username: "validuser",
|
||
// 缺少 Email 和 Password
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
}
|
||
|
||
// TestUserUpdateValidation 测试用户更新验证
|
||
func TestUserUpdateValidation(t *testing.T) {
|
||
validate := validator.New()
|
||
|
||
t.Run("有效的更新请求", func(t *testing.T) {
|
||
email := "newemail@example.com"
|
||
status := constants.UserStatusActive
|
||
req := &model.UpdateUserRequest{
|
||
Email: &email,
|
||
Status: &status,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.NoError(t, err)
|
||
})
|
||
|
||
t.Run("无效的邮箱格式", func(t *testing.T) {
|
||
email := "invalid-email"
|
||
req := &model.UpdateUserRequest{
|
||
Email: &email,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("无效的状态值", func(t *testing.T) {
|
||
status := "invalid_status"
|
||
req := &model.UpdateUserRequest{
|
||
Status: &status,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("空更新请求", func(t *testing.T) {
|
||
req := &model.UpdateUserRequest{}
|
||
|
||
err := validate.Struct(req)
|
||
assert.NoError(t, err) // 空更新请求应该是有效的
|
||
})
|
||
}
|
||
|
||
// TestOrderValidation 测试订单模型验证
|
||
func TestOrderValidation(t *testing.T) {
|
||
validate := validator.New()
|
||
|
||
t.Run("有效的创建订单请求", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
Remark: "测试订单",
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.NoError(t, err)
|
||
})
|
||
|
||
t.Run("订单号太短", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-123", // 少于 10 个字符
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("订单号太长", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-12345678901234567890123456789012345678901234567890", // 超过 50 个字符
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("用户ID无效", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 0, // 用户ID必须大于0
|
||
Amount: 10000,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("金额为负数", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 1,
|
||
Amount: -1000, // 金额不能为负数
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
|
||
t.Run("缺少必填字段", func(t *testing.T) {
|
||
req := &model.CreateOrderRequest{
|
||
OrderID: "ORD-2025-001",
|
||
// 缺少 UserID 和 Amount
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
}
|
||
|
||
// TestOrderUpdateValidation 测试订单更新验证
|
||
func TestOrderUpdateValidation(t *testing.T) {
|
||
validate := validator.New()
|
||
|
||
t.Run("有效的更新请求", func(t *testing.T) {
|
||
status := constants.OrderStatusPaid
|
||
remark := "已支付"
|
||
req := &model.UpdateOrderRequest{
|
||
Status: &status,
|
||
Remark: &remark,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.NoError(t, err)
|
||
})
|
||
|
||
t.Run("无效的状态值", func(t *testing.T) {
|
||
status := "invalid_status"
|
||
req := &model.UpdateOrderRequest{
|
||
Status: &status,
|
||
}
|
||
|
||
err := validate.Struct(req)
|
||
assert.Error(t, err)
|
||
})
|
||
}
|
||
|
||
// TestUserModel 测试用户模型
|
||
func TestUserModel(t *testing.T) {
|
||
t.Run("创建用户模型", func(t *testing.T) {
|
||
user := &model.User{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Password: "hashedpassword",
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
|
||
assert.Equal(t, "testuser", user.Username)
|
||
assert.Equal(t, "test@example.com", user.Email)
|
||
assert.Equal(t, constants.UserStatusActive, user.Status)
|
||
})
|
||
|
||
t.Run("用户表名", func(t *testing.T) {
|
||
user := &model.User{}
|
||
assert.Equal(t, "tb_user", user.TableName())
|
||
})
|
||
|
||
t.Run("软删除字段", func(t *testing.T) {
|
||
user := &model.User{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Password: "hashedpassword",
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
|
||
// DeletedAt 应该是 nil (未删除)
|
||
assert.True(t, user.DeletedAt.Time.IsZero())
|
||
})
|
||
|
||
t.Run("LastLoginAt 可选字段", func(t *testing.T) {
|
||
user := &model.User{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Password: "hashedpassword",
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
|
||
assert.Nil(t, user.LastLoginAt)
|
||
|
||
// 设置登录时间
|
||
now := time.Now()
|
||
user.LastLoginAt = &now
|
||
assert.NotNil(t, user.LastLoginAt)
|
||
assert.Equal(t, now, *user.LastLoginAt)
|
||
})
|
||
}
|
||
|
||
// TestOrderModel 测试订单模型
|
||
func TestOrderModel(t *testing.T) {
|
||
t.Run("创建订单模型", func(t *testing.T) {
|
||
order := &model.Order{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
Status: constants.OrderStatusPending,
|
||
Remark: "测试订单",
|
||
}
|
||
|
||
assert.Equal(t, "ORD-2025-001", order.OrderID)
|
||
assert.Equal(t, uint(1), order.UserID)
|
||
assert.Equal(t, int64(10000), order.Amount)
|
||
assert.Equal(t, constants.OrderStatusPending, order.Status)
|
||
})
|
||
|
||
t.Run("订单表名", func(t *testing.T) {
|
||
order := &model.Order{}
|
||
assert.Equal(t, "tb_order", order.TableName())
|
||
})
|
||
|
||
t.Run("可选时间字段", func(t *testing.T) {
|
||
order := &model.Order{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
Status: constants.OrderStatusPending,
|
||
}
|
||
|
||
assert.Nil(t, order.PaidAt)
|
||
assert.Nil(t, order.CompletedAt)
|
||
|
||
// 设置支付时间
|
||
now := time.Now()
|
||
order.PaidAt = &now
|
||
assert.NotNil(t, order.PaidAt)
|
||
assert.Equal(t, now, *order.PaidAt)
|
||
})
|
||
}
|
||
|
||
// TestBaseModel 测试基础模型
|
||
func TestBaseModel(t *testing.T) {
|
||
t.Run("BaseModel 字段", func(t *testing.T) {
|
||
user := &model.User{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Password: "hashedpassword",
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
|
||
// ID 应该是 0 (未保存)
|
||
assert.Zero(t, user.ID)
|
||
|
||
// 时间戳应该是零值
|
||
assert.True(t, user.CreatedAt.IsZero())
|
||
assert.True(t, user.UpdatedAt.IsZero())
|
||
})
|
||
}
|
||
|
||
// TestUserStatusConstants 测试用户状态常量
|
||
func TestUserStatusConstants(t *testing.T) {
|
||
t.Run("用户状态常量定义", func(t *testing.T) {
|
||
assert.Equal(t, "active", constants.UserStatusActive)
|
||
assert.Equal(t, "inactive", constants.UserStatusInactive)
|
||
assert.Equal(t, "suspended", constants.UserStatusSuspended)
|
||
})
|
||
|
||
t.Run("用户状态验证", func(t *testing.T) {
|
||
validStatuses := []string{
|
||
constants.UserStatusActive,
|
||
constants.UserStatusInactive,
|
||
constants.UserStatusSuspended,
|
||
}
|
||
|
||
for _, status := range validStatuses {
|
||
user := &model.User{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Password: "hashedpassword",
|
||
Status: status,
|
||
}
|
||
assert.Contains(t, validStatuses, user.Status)
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestOrderStatusConstants 测试订单状态常量
|
||
func TestOrderStatusConstants(t *testing.T) {
|
||
t.Run("订单状态常量定义", func(t *testing.T) {
|
||
assert.Equal(t, "pending", constants.OrderStatusPending)
|
||
assert.Equal(t, "paid", constants.OrderStatusPaid)
|
||
assert.Equal(t, "processing", constants.OrderStatusProcessing)
|
||
assert.Equal(t, "completed", constants.OrderStatusCompleted)
|
||
assert.Equal(t, "cancelled", constants.OrderStatusCancelled)
|
||
})
|
||
|
||
t.Run("订单状态流转", func(t *testing.T) {
|
||
order := &model.Order{
|
||
OrderID: "ORD-2025-001",
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
Status: constants.OrderStatusPending,
|
||
}
|
||
|
||
// 订单状态流转:pending -> paid -> processing -> completed
|
||
assert.Equal(t, constants.OrderStatusPending, order.Status)
|
||
|
||
order.Status = constants.OrderStatusPaid
|
||
assert.Equal(t, constants.OrderStatusPaid, order.Status)
|
||
|
||
order.Status = constants.OrderStatusProcessing
|
||
assert.Equal(t, constants.OrderStatusProcessing, order.Status)
|
||
|
||
order.Status = constants.OrderStatusCompleted
|
||
assert.Equal(t, constants.OrderStatusCompleted, order.Status)
|
||
})
|
||
|
||
t.Run("订单取消", func(t *testing.T) {
|
||
order := &model.Order{
|
||
OrderID: "ORD-2025-002",
|
||
UserID: 1,
|
||
Amount: 10000,
|
||
Status: constants.OrderStatusPending,
|
||
}
|
||
|
||
// 从任何状态都可以取消
|
||
order.Status = constants.OrderStatusCancelled
|
||
assert.Equal(t, constants.OrderStatusCancelled, order.Status)
|
||
})
|
||
}
|
||
|
||
// TestUserResponse 测试用户响应模型
|
||
func TestUserResponse(t *testing.T) {
|
||
t.Run("创建用户响应", func(t *testing.T) {
|
||
now := time.Now()
|
||
resp := &model.UserResponse{
|
||
ID: 1,
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Status: constants.UserStatusActive,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
|
||
assert.Equal(t, uint(1), resp.ID)
|
||
assert.Equal(t, "testuser", resp.Username)
|
||
assert.Equal(t, "test@example.com", resp.Email)
|
||
assert.Equal(t, constants.UserStatusActive, resp.Status)
|
||
})
|
||
|
||
t.Run("用户响应不包含密码", func(t *testing.T) {
|
||
// UserResponse 结构体不应该包含 Password 字段
|
||
resp := &model.UserResponse{
|
||
ID: 1,
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Status: constants.UserStatusActive,
|
||
}
|
||
|
||
// 验证结构体大小合理 (不包含密码字段)
|
||
assert.NotNil(t, resp)
|
||
})
|
||
}
|
||
|
||
// TestListResponse 测试列表响应模型
|
||
func TestListResponse(t *testing.T) {
|
||
t.Run("用户列表响应", func(t *testing.T) {
|
||
users := []model.UserResponse{
|
||
{ID: 1, Username: "user1", Email: "user1@example.com", Status: constants.UserStatusActive},
|
||
{ID: 2, Username: "user2", Email: "user2@example.com", Status: constants.UserStatusActive},
|
||
}
|
||
|
||
resp := &model.ListUsersResponse{
|
||
Users: users,
|
||
Page: 1,
|
||
PageSize: 20,
|
||
Total: 100,
|
||
TotalPages: 5,
|
||
}
|
||
|
||
assert.Equal(t, 2, len(resp.Users))
|
||
assert.Equal(t, 1, resp.Page)
|
||
assert.Equal(t, 20, resp.PageSize)
|
||
assert.Equal(t, int64(100), resp.Total)
|
||
assert.Equal(t, 5, resp.TotalPages)
|
||
})
|
||
|
||
t.Run("订单列表响应", func(t *testing.T) {
|
||
orders := []model.OrderResponse{
|
||
{ID: 1, OrderID: "ORD-001", UserID: 1, Amount: 10000, Status: constants.OrderStatusPending},
|
||
{ID: 2, OrderID: "ORD-002", UserID: 1, Amount: 20000, Status: constants.OrderStatusPaid},
|
||
}
|
||
|
||
resp := &model.ListOrdersResponse{
|
||
Orders: orders,
|
||
Page: 1,
|
||
PageSize: 20,
|
||
Total: 50,
|
||
TotalPages: 3,
|
||
}
|
||
|
||
assert.Equal(t, 2, len(resp.Orders))
|
||
assert.Equal(t, 1, resp.Page)
|
||
assert.Equal(t, 20, resp.PageSize)
|
||
assert.Equal(t, int64(50), resp.Total)
|
||
assert.Equal(t, 3, resp.TotalPages)
|
||
})
|
||
}
|
||
|
||
// TestFieldTags 测试字段标签
|
||
func TestFieldTags(t *testing.T) {
|
||
t.Run("User GORM 标签", func(t *testing.T) {
|
||
user := &model.User{}
|
||
|
||
// 验证 TableName 方法存在
|
||
tableName := user.TableName()
|
||
assert.Equal(t, "tb_user", tableName)
|
||
})
|
||
|
||
t.Run("Order GORM 标签", func(t *testing.T) {
|
||
order := &model.Order{}
|
||
|
||
// 验证 TableName 方法存在
|
||
tableName := order.TableName()
|
||
assert.Equal(t, "tb_order", tableName)
|
||
})
|
||
}
|