Files
junhong_cmp_fiber/tests/unit/account_model_test.go
huang 1b9080e3ab 实现角色权限体系重构
本次提交完成了角色权限体系的重构,主要包括:

1. 数据库迁移
   - 添加 tb_permission.platform 字段(all/web/h5)
   - 更新 tb_role.role_type 注释(1=平台角色,2=客户角色)

2. GORM 模型更新
   - Permission 模型添加 Platform 字段
   - Role 模型更新 RoleType 注释

3. 常量定义
   - 新增角色类型常量(RoleTypePlatform, RoleTypeCustomer)
   - 新增权限端口常量(PlatformAll, PlatformWeb, PlatformH5)
   - 添加角色类型与用户类型匹配规则函数

4. Store 层实现
   - Permission Store 支持按 platform 过滤
   - Account Role Store 添加 CountByAccountID 方法

5. Service 层实现
   - 角色分配支持类型匹配校验
   - 角色分配支持数量限制(超级管理员0个,平台用户无限制,代理/企业1个)
   - Permission Service 支持 platform 过滤

6. 权限校验中间件
   - 实现 RequirePermission、RequireAnyPermission、RequireAllPermissions
   - 支持 platform 字段过滤
   - 支持跳过超级管理员检查

7. 测试用例
   - 角色类型匹配规则单元测试
   - 角色分配数量限制单元测试
   - 权限 platform 过滤单元测试
   - 权限校验中间件集成测试(占位)

8. 代码清理
   - 删除过时的 subordinate 测试文件
   - 移除 Account.ParentID 相关引用
   - 更新 DTO 验证规则

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 09:51:52 +08:00

271 lines
7.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package unit
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
// TestAccountModel_Create 测试创建账号
func TestAccountModel_Create(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
t.Run("创建 root 账号", func(t *testing.T) {
account := &model.Account{
Username: "root_user",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypeSuperAdmin,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
assert.NotZero(t, account.ID)
assert.NotZero(t, account.CreatedAt)
assert.NotZero(t, account.UpdatedAt)
})
// 注意parent_id 字段已被移除,层级关系通过 shop_id 和 enterprise_id 维护
t.Run("创建带 shop_id 的账号", func(t *testing.T) {
shopID := uint(100)
account := &model.Account{
Username: "shop_user",
Phone: "13800000004",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
ShopID: &shopID,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
assert.NotNil(t, account.ShopID)
assert.Equal(t, uint(100), *account.ShopID)
})
}
// TestAccountModel_GetByID 测试根据 ID 查询账号
func TestAccountModel_GetByID(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建测试账号
account := &model.Account{
Username: "test_user",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
t.Run("查询存在的账号", func(t *testing.T) {
found, err := store.GetByID(ctx, account.ID)
require.NoError(t, err)
assert.Equal(t, account.Username, found.Username)
assert.Equal(t, account.Phone, found.Phone)
assert.Equal(t, account.UserType, found.UserType)
})
t.Run("查询不存在的账号", func(t *testing.T) {
_, err := store.GetByID(ctx, 99999)
assert.Error(t, err)
})
}
// TestAccountModel_GetByUsername 测试根据用户名查询账号
func TestAccountModel_GetByUsername(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建测试账号
account := &model.Account{
Username: "unique_user",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
t.Run("根据用户名查询", func(t *testing.T) {
found, err := store.GetByUsername(ctx, "unique_user")
require.NoError(t, err)
assert.Equal(t, account.ID, found.ID)
})
t.Run("查询不存在的用户名", func(t *testing.T) {
_, err := store.GetByUsername(ctx, "nonexistent")
assert.Error(t, err)
})
}
// TestAccountModel_GetByPhone 测试根据手机号查询账号
func TestAccountModel_GetByPhone(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建测试账号
account := &model.Account{
Username: "phone_user",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
t.Run("根据手机号查询", func(t *testing.T) {
found, err := store.GetByPhone(ctx, "13800000001")
require.NoError(t, err)
assert.Equal(t, account.ID, found.ID)
})
t.Run("查询不存在的手机号", func(t *testing.T) {
_, err := store.GetByPhone(ctx, "99900000000")
assert.Error(t, err)
})
}
// TestAccountModel_Update 测试更新账号
func TestAccountModel_Update(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建测试账号
account := &model.Account{
Username: "update_user",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
t.Run("更新账号状态", func(t *testing.T) {
account.Status = constants.StatusDisabled
account.Updater = 2
err := store.Update(ctx, account)
require.NoError(t, err)
// 验证更新
found, err := store.GetByID(ctx, account.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, found.Status)
assert.Equal(t, uint(2), found.Updater)
})
}
// TestAccountModel_List 测试查询账号列表
func TestAccountModel_List(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建多个测试账号
for i := 1; i <= 5; i++ {
account := &model.Account{
Username: testutils.GenerateUsername("list_user", i),
Phone: testutils.GeneratePhone("138", i),
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
}
t.Run("分页查询", func(t *testing.T) {
accounts, total, err := store.List(ctx, nil, nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(accounts), 5)
assert.GreaterOrEqual(t, total, int64(5))
})
t.Run("带过滤条件查询", func(t *testing.T) {
filters := map[string]interface{}{
"user_type": constants.UserTypePlatform,
}
accounts, _, err := store.List(ctx, nil, filters)
require.NoError(t, err)
for _, acc := range accounts {
assert.Equal(t, constants.UserTypePlatform, acc.UserType)
}
})
}
// TestAccountModel_UniqueConstraints 测试唯一约束
func TestAccountModel_UniqueConstraints(t *testing.T) {
db, redisClient := testutils.SetupTestDB(t)
defer testutils.TeardownTestDB(t, db, redisClient)
store := postgres.NewAccountStore(db, redisClient)
ctx := context.Background()
// 创建测试账号
account := &model.Account{
Username: "unique_test",
Phone: "13800000001",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, account)
require.NoError(t, err)
t.Run("重复用户名应失败", func(t *testing.T) {
duplicate := &model.Account{
Username: "unique_test", // 重复
Phone: "13800000002",
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, duplicate)
assert.Error(t, err)
})
t.Run("重复手机号应失败", func(t *testing.T) {
duplicate := &model.Account{
Username: "unique_test2",
Phone: "13800000001", // 重复
Password: "hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
err := store.Create(ctx, duplicate)
assert.Error(t, err)
})
}