feat: 实现企业卡授权和授权记录管理功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
主要功能: - 添加企业卡授权/回收接口 (POST /enterprises/:id/allocate-cards, recall-cards) - 添加授权记录管理接口 (GET/PUT /authorizations) - 实现代理用户数据权限过滤(只能查看自己店铺下企业的授权记录) - 添加 GORM callback 支持授权记录表的数据权限过滤 技术改进: - 原生 SQL 查询手动添加数据权限过滤(ListWithJoin, GetByIDWithJoin) - 移除卡授权预检接口(allocate-cards/preview),保留内部方法 - 完善单元测试和集成测试覆盖
This commit is contained in:
@@ -128,3 +128,11 @@ const DefaultAdminPassword = "Admin@123456"
|
||||
|
||||
// DefaultAdminPhone 默认超级管理员手机号
|
||||
const DefaultAdminPhone = "13800000000"
|
||||
|
||||
// ======== 企业卡授权相关常量 ========
|
||||
|
||||
// AuthorizerType 授权人类型
|
||||
const (
|
||||
AuthorizerTypePlatform = UserTypePlatform // 平台用户授权(2)
|
||||
AuthorizerTypeAgent = UserTypeAgent // 代理账号授权(3)
|
||||
)
|
||||
|
||||
@@ -60,13 +60,19 @@ const (
|
||||
CodeWalletNotFound = 1053 // 钱包不存在
|
||||
|
||||
// IoT 卡相关错误 (1070-1089)
|
||||
CodeIotCardNotFound = 1070 // IoT 卡不存在
|
||||
CodeIotCardBoundToDevice = 1071 // IoT 卡已绑定设备
|
||||
CodeIotCardStatusNotAllowed = 1072 // 卡状态不允许此操作
|
||||
CodeAssetAllocationRecordNotFound = 1073 // 分配记录不存在
|
||||
CodeNotDirectSubordinate = 1074 // 非直属下级店铺
|
||||
CodeCannotAllocateToSelf = 1075 // 不能分配给自己
|
||||
CodeCannotRecallFromSelf = 1076 // 不能从自己回收
|
||||
CodeIotCardNotFound = 1070 // IoT 卡不存在
|
||||
CodeIotCardBoundToDevice = 1071 // IoT 卡已绑定设备
|
||||
CodeIotCardStatusNotAllowed = 1072 // 卡状态不允许此操作
|
||||
CodeAssetAllocationRecordNotFound = 1073 // 分配记录不存在
|
||||
CodeNotDirectSubordinate = 1074 // 非直属下级店铺
|
||||
CodeCannotAllocateToSelf = 1075 // 不能分配给自己
|
||||
CodeCannotRecallFromSelf = 1076 // 不能从自己回收
|
||||
CodeCardAlreadyAuthorized = 1077 // 卡已授权给该企业
|
||||
CodeCardNotAuthorized = 1078 // 卡未授权给该企业
|
||||
CodeCannotAuthorizeOthersCard = 1079 // 不能授权非自己的卡
|
||||
CodeCannotRevokeOthersAuthorization = 1080 // 不能回收非自己创建的授权
|
||||
CodeCannotAuthorizeBoundCard = 1081 // 不能授权已绑定设备的卡
|
||||
CodeCannotAuthorizeToOthersEnterprise = 1082 // 不能授权给非自己的企业
|
||||
|
||||
// 对象存储相关错误 (1090-1099)
|
||||
CodeStorageNotConfigured = 1090 // 对象存储服务未配置
|
||||
@@ -138,6 +144,12 @@ var allErrorCodes = []int{
|
||||
CodeNotDirectSubordinate,
|
||||
CodeCannotAllocateToSelf,
|
||||
CodeCannotRecallFromSelf,
|
||||
CodeCardAlreadyAuthorized,
|
||||
CodeCardNotAuthorized,
|
||||
CodeCannotAuthorizeOthersCard,
|
||||
CodeCannotRevokeOthersAuthorization,
|
||||
CodeCannotAuthorizeBoundCard,
|
||||
CodeCannotAuthorizeToOthersEnterprise,
|
||||
CodeStorageNotConfigured,
|
||||
CodeStorageUploadFailed,
|
||||
CodeStorageDownloadFailed,
|
||||
@@ -162,68 +174,74 @@ func init() {
|
||||
|
||||
// errorMessages 错误消息映射表(中文)
|
||||
var errorMessages = map[int]string{
|
||||
CodeSuccess: "成功",
|
||||
CodeInvalidParam: "参数验证失败",
|
||||
CodeMissingToken: "缺失认证令牌",
|
||||
CodeInvalidToken: "无效或过期的令牌",
|
||||
CodeUnauthorized: "未授权访问",
|
||||
CodeForbidden: "禁止访问",
|
||||
CodeNotFound: "资源未找到",
|
||||
CodeConflict: "资源冲突",
|
||||
CodeTooManyRequests: "请求过多,请稍后重试",
|
||||
CodeRequestTooLarge: "请求体过大",
|
||||
CodeAccountNotFound: "账号不存在",
|
||||
CodeAccountDisabled: "账号已禁用",
|
||||
CodeAccountDeleted: "账号已删除",
|
||||
CodeUsernameExists: "用户名已存在",
|
||||
CodePhoneExists: "手机号已存在",
|
||||
CodeInvalidPassword: "密码格式不正确",
|
||||
CodePasswordTooWeak: "密码强度不足",
|
||||
CodeParentIDRequired: "非 root 用户必须提供上级账号",
|
||||
CodeInvalidParentID: "上级账号不存在或无效",
|
||||
CodeCannotModifyParent: "禁止修改上级账号",
|
||||
CodeCannotModifyUserType: "禁止修改用户类型",
|
||||
CodeRoleNotFound: "角色不存在",
|
||||
CodeRoleNameExists: "角色名称已存在",
|
||||
CodePermissionNotFound: "权限不存在",
|
||||
CodePermCodeExists: "权限编码已存在",
|
||||
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
||||
CodeRoleAlreadyAssigned: "角色已分配",
|
||||
CodePermAlreadyAssigned: "权限已分配",
|
||||
CodeShopNotFound: "店铺不存在",
|
||||
CodeShopCodeExists: "店铺编号已存在",
|
||||
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
||||
CodeEnterpriseNotFound: "企业不存在",
|
||||
CodeEnterpriseCodeExists: "企业编号已存在",
|
||||
CodeCustomerNotFound: "个人客户不存在",
|
||||
CodeCustomerPhoneExists: "个人客户手机号已存在",
|
||||
CodeInvalidStatus: "状态不允许此操作",
|
||||
CodeInsufficientBalance: "余额不足",
|
||||
CodeWithdrawalNotFound: "提现申请不存在",
|
||||
CodeWalletNotFound: "钱包不存在",
|
||||
CodeIotCardNotFound: "IoT 卡不存在",
|
||||
CodeIotCardBoundToDevice: "IoT 卡已绑定设备,不能单独操作",
|
||||
CodeIotCardStatusNotAllowed: "卡状态不允许此操作",
|
||||
CodeAssetAllocationRecordNotFound: "分配记录不存在",
|
||||
CodeNotDirectSubordinate: "只能操作直属下级店铺",
|
||||
CodeCannotAllocateToSelf: "不能分配给自己",
|
||||
CodeCannotRecallFromSelf: "不能从自己回收",
|
||||
CodeStorageNotConfigured: "对象存储服务未配置",
|
||||
CodeStorageUploadFailed: "文件上传失败",
|
||||
CodeStorageDownloadFailed: "文件下载失败",
|
||||
CodeStorageFileNotFound: "文件不存在",
|
||||
CodeStorageInvalidPurpose: "不支持的文件用途",
|
||||
CodeStorageInvalidFileType: "不支持的文件类型",
|
||||
CodeInvalidCredentials: "用户名或密码错误",
|
||||
CodeAccountLocked: "账号已锁定",
|
||||
CodePasswordExpired: "密码已过期",
|
||||
CodeInvalidOldPassword: "旧密码错误",
|
||||
CodeInternalError: "内部服务器错误",
|
||||
CodeDatabaseError: "数据库错误",
|
||||
CodeRedisError: "缓存服务错误",
|
||||
CodeServiceUnavailable: "服务暂时不可用",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeTaskQueueError: "任务队列错误",
|
||||
CodeSuccess: "成功",
|
||||
CodeInvalidParam: "参数验证失败",
|
||||
CodeMissingToken: "缺失认证令牌",
|
||||
CodeInvalidToken: "无效或过期的令牌",
|
||||
CodeUnauthorized: "未授权访问",
|
||||
CodeForbidden: "禁止访问",
|
||||
CodeNotFound: "资源未找到",
|
||||
CodeConflict: "资源冲突",
|
||||
CodeTooManyRequests: "请求过多,请稍后重试",
|
||||
CodeRequestTooLarge: "请求体过大",
|
||||
CodeAccountNotFound: "账号不存在",
|
||||
CodeAccountDisabled: "账号已禁用",
|
||||
CodeAccountDeleted: "账号已删除",
|
||||
CodeUsernameExists: "用户名已存在",
|
||||
CodePhoneExists: "手机号已存在",
|
||||
CodeInvalidPassword: "密码格式不正确",
|
||||
CodePasswordTooWeak: "密码强度不足",
|
||||
CodeParentIDRequired: "非 root 用户必须提供上级账号",
|
||||
CodeInvalidParentID: "上级账号不存在或无效",
|
||||
CodeCannotModifyParent: "禁止修改上级账号",
|
||||
CodeCannotModifyUserType: "禁止修改用户类型",
|
||||
CodeRoleNotFound: "角色不存在",
|
||||
CodeRoleNameExists: "角色名称已存在",
|
||||
CodePermissionNotFound: "权限不存在",
|
||||
CodePermCodeExists: "权限编码已存在",
|
||||
CodeInvalidPermCode: "权限编码格式不正确(应为 module:action 格式)",
|
||||
CodeRoleAlreadyAssigned: "角色已分配",
|
||||
CodePermAlreadyAssigned: "权限已分配",
|
||||
CodeShopNotFound: "店铺不存在",
|
||||
CodeShopCodeExists: "店铺编号已存在",
|
||||
CodeShopLevelExceeded: "店铺层级不能超过 7 级",
|
||||
CodeEnterpriseNotFound: "企业不存在",
|
||||
CodeEnterpriseCodeExists: "企业编号已存在",
|
||||
CodeCustomerNotFound: "个人客户不存在",
|
||||
CodeCustomerPhoneExists: "个人客户手机号已存在",
|
||||
CodeInvalidStatus: "状态不允许此操作",
|
||||
CodeInsufficientBalance: "余额不足",
|
||||
CodeWithdrawalNotFound: "提现申请不存在",
|
||||
CodeWalletNotFound: "钱包不存在",
|
||||
CodeIotCardNotFound: "IoT 卡不存在",
|
||||
CodeIotCardBoundToDevice: "IoT 卡已绑定设备,不能单独操作",
|
||||
CodeIotCardStatusNotAllowed: "卡状态不允许此操作",
|
||||
CodeAssetAllocationRecordNotFound: "分配记录不存在",
|
||||
CodeNotDirectSubordinate: "只能操作直属下级店铺",
|
||||
CodeCannotAllocateToSelf: "不能分配给自己",
|
||||
CodeCannotRecallFromSelf: "不能从自己回收",
|
||||
CodeCardAlreadyAuthorized: "卡已授权给该企业",
|
||||
CodeCardNotAuthorized: "卡未授权给该企业",
|
||||
CodeCannotAuthorizeOthersCard: "不能授权非自己的卡",
|
||||
CodeCannotRevokeOthersAuthorization: "不能回收非自己创建的授权",
|
||||
CodeCannotAuthorizeBoundCard: "不能授权已绑定设备的卡",
|
||||
CodeCannotAuthorizeToOthersEnterprise: "不能授权给非自己的企业",
|
||||
CodeStorageNotConfigured: "对象存储服务未配置",
|
||||
CodeStorageUploadFailed: "文件上传失败",
|
||||
CodeStorageDownloadFailed: "文件下载失败",
|
||||
CodeStorageFileNotFound: "文件不存在",
|
||||
CodeStorageInvalidPurpose: "不支持的文件用途",
|
||||
CodeStorageInvalidFileType: "不支持的文件类型",
|
||||
CodeInvalidCredentials: "用户名或密码错误",
|
||||
CodeAccountLocked: "账号已锁定",
|
||||
CodePasswordExpired: "密码已过期",
|
||||
CodeInvalidOldPassword: "旧密码错误",
|
||||
CodeInternalError: "内部服务器错误",
|
||||
CodeDatabaseError: "数据库错误",
|
||||
CodeRedisError: "缓存服务错误",
|
||||
CodeServiceUnavailable: "服务暂时不可用",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeTaskQueueError: "任务队列错误",
|
||||
}
|
||||
|
||||
// GetMessage 获取错误码对应的消息
|
||||
|
||||
@@ -101,6 +101,18 @@ func RegisterDataPermissionCallback(db *gorm.DB, shopStore ShopStoreInterface) e
|
||||
if userType == constants.UserTypeAgent {
|
||||
tableName := schema.Table
|
||||
|
||||
// 特殊处理:授权记录表(通过企业归属过滤,不含下级店铺)
|
||||
if tableName == "tb_enterprise_card_authorization" {
|
||||
if shopID == 0 {
|
||||
// 代理用户没有 shop_id,返回空结果
|
||||
tx.Where("1 = 0")
|
||||
return
|
||||
}
|
||||
// 只能看到自己店铺下企业的授权记录(不包含下级店铺)
|
||||
tx.Where("enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = ? AND deleted_at IS NULL)", shopID)
|
||||
return
|
||||
}
|
||||
|
||||
// 特殊处理:标签表和资源标签表(包含全局标签)
|
||||
if tableName == "tb_tag" || tableName == "tb_resource_tag" {
|
||||
if shopID == 0 {
|
||||
|
||||
@@ -3,6 +3,7 @@ package gorm
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
@@ -846,3 +847,328 @@ func TestTagPermission_CrossIsolation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 企业卡授权表数据权限过滤测试(tb_enterprise_card_authorization 表)
|
||||
// ============================================================
|
||||
|
||||
// EnterpriseModel 模拟企业表,用于授权表过滤测试
|
||||
type EnterpriseModel struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
OwnerShopID *uint `gorm:"column:owner_shop_id"`
|
||||
DeletedAt *time.Time `gorm:"column:deleted_at"`
|
||||
Name string
|
||||
}
|
||||
|
||||
func (EnterpriseModel) TableName() string {
|
||||
return "tb_enterprise"
|
||||
}
|
||||
|
||||
// AuthorizationModel 模拟企业卡授权表结构
|
||||
type AuthorizationModel struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
EnterpriseID uint `gorm:"column:enterprise_id"`
|
||||
CardID uint `gorm:"column:card_id"`
|
||||
AuthorizedBy uint `gorm:"column:authorized_by"`
|
||||
AuthorizedAt time.Time `gorm:"column:authorized_at"`
|
||||
AuthorizerType int `gorm:"column:authorizer_type"`
|
||||
RevokedBy *uint `gorm:"column:revoked_by"`
|
||||
RevokedAt *time.Time `gorm:"column:revoked_at"`
|
||||
Remark string `gorm:"column:remark"`
|
||||
}
|
||||
|
||||
func (AuthorizationModel) TableName() string {
|
||||
return "tb_enterprise_card_authorization"
|
||||
}
|
||||
|
||||
// setupAuthorizationTestDB 创建授权表测试数据库和数据
|
||||
func setupAuthorizationTestDB(t *testing.T) (*gorm.DB, *mockShopStore) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 创建测试表
|
||||
err = db.AutoMigrate(&EnterpriseModel{}, &AuthorizationModel{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 插入企业测试数据
|
||||
// 1. 店铺 100 下的企业
|
||||
db.Create(&EnterpriseModel{ID: 1, OwnerShopID: uintPtr(100), Name: "企业A-店铺100"})
|
||||
db.Create(&EnterpriseModel{ID: 2, OwnerShopID: uintPtr(100), Name: "企业B-店铺100"})
|
||||
// 2. 店铺 200(店铺100的下级)下的企业
|
||||
db.Create(&EnterpriseModel{ID: 3, OwnerShopID: uintPtr(200), Name: "企业C-店铺200"})
|
||||
// 3. 店铺 300(其他店铺)下的企业
|
||||
db.Create(&EnterpriseModel{ID: 4, OwnerShopID: uintPtr(300), Name: "企业D-店铺300"})
|
||||
// 4. 平台直属企业(无店铺归属)
|
||||
db.Create(&EnterpriseModel{ID: 5, OwnerShopID: nil, Name: "企业E-平台直属"})
|
||||
|
||||
now := time.Now()
|
||||
// 插入授权记录测试数据
|
||||
// 1. 企业1的授权记录(店铺100)
|
||||
db.Create(&AuthorizationModel{ID: 1, EnterpriseID: 1, CardID: 101, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 3})
|
||||
db.Create(&AuthorizationModel{ID: 2, EnterpriseID: 1, CardID: 102, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 3})
|
||||
// 2. 企业2的授权记录(店铺100)
|
||||
db.Create(&AuthorizationModel{ID: 3, EnterpriseID: 2, CardID: 201, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 3})
|
||||
// 3. 企业3的授权记录(店铺200 - 下级店铺)
|
||||
db.Create(&AuthorizationModel{ID: 4, EnterpriseID: 3, CardID: 301, AuthorizedBy: 2, AuthorizedAt: now, AuthorizerType: 3})
|
||||
// 4. 企业4的授权记录(店铺300 - 其他店铺)
|
||||
db.Create(&AuthorizationModel{ID: 5, EnterpriseID: 4, CardID: 401, AuthorizedBy: 3, AuthorizedAt: now, AuthorizerType: 3})
|
||||
db.Create(&AuthorizationModel{ID: 6, EnterpriseID: 4, CardID: 402, AuthorizedBy: 3, AuthorizedAt: now, AuthorizerType: 3})
|
||||
// 5. 企业5的授权记录(平台直属)
|
||||
db.Create(&AuthorizationModel{ID: 7, EnterpriseID: 5, CardID: 501, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2})
|
||||
|
||||
// 创建 mock ShopStore
|
||||
// 店铺 100 的下级店铺包括 100 和 200(不含 300)
|
||||
mockStore := &mockShopStore{
|
||||
subordinateShopIDs: []uint{100, 200},
|
||||
}
|
||||
|
||||
return db, mockStore
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_SuperAdmin 测试超级管理员查询授权记录(应看到所有记录)
|
||||
func TestAuthorizationPermission_SuperAdmin(t *testing.T) {
|
||||
db, mockStore := setupAuthorizationTestDB(t)
|
||||
|
||||
// 注册 Callback
|
||||
err := RegisterDataPermissionCallback(db, mockStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 设置超级管理员 context
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypeSuperAdmin,
|
||||
ShopID: 0,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询授权记录
|
||||
var auths []AuthorizationModel
|
||||
err = db.WithContext(ctx).Find(&auths).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 超级管理员应该看到所有 7 条记录
|
||||
assert.Equal(t, 7, len(auths), "超级管理员应该看到所有授权记录")
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_Platform 测试平台用户查询授权记录(应看到所有记录)
|
||||
func TestAuthorizationPermission_Platform(t *testing.T) {
|
||||
db, mockStore := setupAuthorizationTestDB(t)
|
||||
|
||||
// 注册 Callback
|
||||
err := RegisterDataPermissionCallback(db, mockStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 设置平台用户 context
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypePlatform,
|
||||
ShopID: 0,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询授权记录
|
||||
var auths []AuthorizationModel
|
||||
err = db.WithContext(ctx).Find(&auths).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 平台用户应该看到所有 7 条记录
|
||||
assert.Equal(t, 7, len(auths), "平台用户应该看到所有授权记录")
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_Agent_OwnShopOnly 测试代理用户查询授权记录
|
||||
// 关键业务规则:代理只能看到自己店铺下企业的授权记录,不含下级店铺
|
||||
func TestAuthorizationPermission_Agent_OwnShopOnly(t *testing.T) {
|
||||
db, mockStore := setupAuthorizationTestDB(t)
|
||||
|
||||
// 注册 Callback
|
||||
err := RegisterDataPermissionCallback(db, mockStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 设置代理用户 context(店铺 ID = 100)
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: 100,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询授权记录
|
||||
var auths []AuthorizationModel
|
||||
err = db.WithContext(ctx).Find(&auths).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 代理用户(店铺100)应该只看到:
|
||||
// - 企业1的2条授权记录(ID: 1, 2)
|
||||
// - 企业2的1条授权记录(ID: 3)
|
||||
// 总共 3 条记录
|
||||
// 注意:不含下级店铺200的记录(ID: 4),这是关键业务规则
|
||||
assert.Equal(t, 3, len(auths), "代理用户应该只看到自己店铺下企业的授权记录(不含下级店铺)")
|
||||
|
||||
// 验证授权记录 ID
|
||||
expectedIDs := map[uint]bool{1: true, 2: true, 3: true}
|
||||
for _, auth := range auths {
|
||||
assert.True(t, expectedIDs[auth.ID], "授权记录 ID %d 不应该被代理用户看到", auth.ID)
|
||||
}
|
||||
|
||||
// 验证看不到下级店铺的记录
|
||||
for _, auth := range auths {
|
||||
assert.NotEqual(t, uint(4), auth.ID, "代理用户不应该看到下级店铺的授权记录")
|
||||
}
|
||||
|
||||
// 验证看不到其他店铺的记录
|
||||
for _, auth := range auths {
|
||||
assert.NotEqual(t, uint(5), auth.ID, "代理用户不应该看到其他店铺的授权记录")
|
||||
assert.NotEqual(t, uint(6), auth.ID, "代理用户不应该看到其他店铺的授权记录")
|
||||
}
|
||||
|
||||
// 验证看不到平台直属企业的记录
|
||||
for _, auth := range auths {
|
||||
assert.NotEqual(t, uint(7), auth.ID, "代理用户不应该看到平台直属企业的授权记录")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_Agent_SubordinateShop 测试下级店铺代理查询授权记录
|
||||
// 验证下级店铺代理只能看到自己店铺下企业的授权记录
|
||||
func TestAuthorizationPermission_Agent_SubordinateShop(t *testing.T) {
|
||||
db, _ := setupAuthorizationTestDB(t)
|
||||
|
||||
// 创建 mock ShopStore,店铺 200 只能看到自己
|
||||
mockStore := &mockShopStore{
|
||||
subordinateShopIDs: []uint{200},
|
||||
}
|
||||
|
||||
// 注册 Callback
|
||||
err := RegisterDataPermissionCallback(db, mockStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 设置代理用户 context(店铺 ID = 200,是店铺100的下级)
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||
UserID: 2,
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: 200,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询授权记录
|
||||
var auths []AuthorizationModel
|
||||
err = db.WithContext(ctx).Find(&auths).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 店铺200的代理用户应该只看到:
|
||||
// - 企业3的1条授权记录(ID: 4)
|
||||
// 总共 1 条记录
|
||||
assert.Equal(t, 1, len(auths), "下级店铺代理应该只看到自己店铺下企业的授权记录")
|
||||
|
||||
// 验证授权记录 ID
|
||||
assert.Equal(t, uint(4), auths[0].ID, "应该是企业3的授权记录")
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_Agent_NoShopID 测试没有 ShopID 的代理用户
|
||||
// 预期:返回空结果
|
||||
func TestAuthorizationPermission_Agent_NoShopID(t *testing.T) {
|
||||
db, mockStore := setupAuthorizationTestDB(t)
|
||||
|
||||
// 注册 Callback
|
||||
err := RegisterDataPermissionCallback(db, mockStore)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 设置代理用户 context(没有店铺 ID)
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetUserContext(ctx, &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: 0, // 没有店铺
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询授权记录
|
||||
var auths []AuthorizationModel
|
||||
err = db.WithContext(ctx).Find(&auths).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 没有店铺的代理用户应该看不到任何记录
|
||||
assert.Equal(t, 0, len(auths), "没有店铺的代理用户应该看不到任何授权记录")
|
||||
}
|
||||
|
||||
// TestAuthorizationPermission_Agent_CrossShopIsolation 测试跨店铺隔离
|
||||
// 验证店铺 A 看不到店铺 B 的授权记录
|
||||
func TestAuthorizationPermission_Agent_CrossShopIsolation(t *testing.T) {
|
||||
db, _ := setupAuthorizationTestDB(t)
|
||||
|
||||
// 店铺 100 的 mock
|
||||
mockStore100 := &mockShopStore{
|
||||
subordinateShopIDs: []uint{100},
|
||||
}
|
||||
|
||||
// 店铺 300 的 mock
|
||||
mockStore300 := &mockShopStore{
|
||||
subordinateShopIDs: []uint{300},
|
||||
}
|
||||
|
||||
// 注册 Callback(使用店铺100的mock)
|
||||
err := RegisterDataPermissionCallback(db, mockStore100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 店铺 100 代理用户
|
||||
ctx100 := context.Background()
|
||||
ctx100 = middleware.SetUserContext(ctx100, &middleware.UserContextInfo{
|
||||
UserID: 1,
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: 100,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询店铺100的授权记录
|
||||
var auths100 []AuthorizationModel
|
||||
err = db.WithContext(ctx100).Find(&auths100).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 店铺100应该看到3条记录(企业1和企业2的)
|
||||
assert.Equal(t, 3, len(auths100), "店铺100应该看到自己店铺下企业的授权记录")
|
||||
|
||||
// 重新创建数据库并注册店铺300的 Callback
|
||||
db2, _ := setupAuthorizationTestDB(t)
|
||||
err = RegisterDataPermissionCallback(db2, mockStore300)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 店铺 300 代理用户
|
||||
ctx300 := context.Background()
|
||||
ctx300 = middleware.SetUserContext(ctx300, &middleware.UserContextInfo{
|
||||
UserID: 3,
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: 300,
|
||||
EnterpriseID: 0,
|
||||
CustomerID: 0,
|
||||
})
|
||||
|
||||
// 查询店铺300的授权记录
|
||||
var auths300 []AuthorizationModel
|
||||
err = db2.WithContext(ctx300).Find(&auths300).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 店铺300应该看到2条记录(企业4的)
|
||||
assert.Equal(t, 2, len(auths300), "店铺300应该看到自己店铺下企业的授权记录")
|
||||
|
||||
// 验证店铺100看不到店铺300的记录
|
||||
for _, auth := range auths100 {
|
||||
assert.NotEqual(t, uint(5), auth.ID, "店铺100不应该看到店铺300的授权记录")
|
||||
assert.NotEqual(t, uint(6), auth.ID, "店铺100不应该看到店铺300的授权记录")
|
||||
}
|
||||
|
||||
// 验证店铺300看不到店铺100的记录
|
||||
for _, auth := range auths300 {
|
||||
assert.NotEqual(t, uint(1), auth.ID, "店铺300不应该看到店铺100的授权记录")
|
||||
assert.NotEqual(t, uint(2), auth.ID, "店铺300不应该看到店铺100的授权记录")
|
||||
assert.NotEqual(t, uint(3), auth.ID, "店铺300不应该看到店铺100的授权记录")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user