1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
180 lines
6.4 KiB
TypeScript
180 lines
6.4 KiB
TypeScript
/**
|
||
* 素材删除权限验证测试
|
||
*
|
||
* **Feature: bugfix-dec-2025**
|
||
* **Property 2: 管理员素材删除权限验证**
|
||
* **Property 3: 超级管理员删除权限**
|
||
* **Validates: Requirements 3.1, 3.2, 3.3**
|
||
*
|
||
* @jest-environment node
|
||
*/
|
||
|
||
describe('素材删除权限验证', () => {
|
||
/**
|
||
* 模拟部门层级关系检查
|
||
* @param operatorScope 操作者管理的部门
|
||
* @param targetDepartment 目标素材上传者的部门
|
||
* @returns 是否在管理范围内
|
||
*/
|
||
const isDepartmentWithinScope = (
|
||
operatorScope: string,
|
||
targetDepartment: string
|
||
): boolean => {
|
||
if (!operatorScope || !targetDepartment) return false
|
||
// 同部门或子部门
|
||
return targetDepartment === operatorScope ||
|
||
targetDepartment.startsWith(`${operatorScope}-`)
|
||
}
|
||
|
||
/**
|
||
* 权限验证逻辑
|
||
* @param userId 当前用户ID
|
||
* @param isSuperAdmin 是否为超级管理员
|
||
* @param materialOwnerId 素材所有者ID
|
||
* @param operatorScope 操作者管理的部门范围
|
||
* @param targetDepartment 素材上传者所在部门
|
||
* @returns 是否有权限删除
|
||
*/
|
||
const canDeleteMaterial = (
|
||
userId: string,
|
||
isSuperAdmin: boolean,
|
||
materialOwnerId: string,
|
||
operatorScope: string = '',
|
||
targetDepartment: string = ''
|
||
): boolean => {
|
||
// 超级管理员可以删除任意素材
|
||
if (isSuperAdmin) {
|
||
return true
|
||
}
|
||
// 自己上传的素材
|
||
if (userId === materialOwnerId) {
|
||
return true
|
||
}
|
||
// 检查是否在管理范围内
|
||
if (operatorScope && targetDepartment) {
|
||
return isDepartmentWithinScope(operatorScope, targetDepartment)
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* Property 2: 管理员素材删除权限验证
|
||
* *For any* 管理员用户和任意素材,删除操作应在素材属于该管理员或其管理的部门成员时成功
|
||
* **Feature: bugfix-dec-2025, Property 2: 管理员素材删除权限验证**
|
||
* **Validates: Requirements 3.1, 3.2**
|
||
*/
|
||
describe('Property 2: 管理员素材删除权限验证', () => {
|
||
it('管理员可以删除自己上传的素材', () => {
|
||
const result = canDeleteMaterial('admin-1', false, 'admin-1')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('管理员可以删除自己管理的部门成员上传的素材', () => {
|
||
// admin-1 管理 "设计部",user-1 属于 "设计部"
|
||
const result = canDeleteMaterial('admin-1', false, 'user-1', '设计部', '设计部')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('管理员可以删除子部门成员上传的素材', () => {
|
||
// admin-1 管理 "设计部",user-1 属于 "设计部-UI组"
|
||
const result = canDeleteMaterial('admin-1', false, 'user-1', '设计部', '设计部-UI组')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('管理员不能删除其他部门成员上传的素材', () => {
|
||
// admin-1 管理 "设计部",user-1 属于 "技术部"
|
||
const result = canDeleteMaterial('admin-1', false, 'user-1', '设计部', '技术部')
|
||
expect(result).toBe(false)
|
||
})
|
||
|
||
it('管理员不能删除没有部门信息的其他用户素材', () => {
|
||
const result = canDeleteMaterial('admin-1', false, 'user-1', '', '')
|
||
expect(result).toBe(false)
|
||
})
|
||
|
||
// 属性测试:部门层级关系
|
||
it('对于任意管理员,可以删除自己或管理范围内的素材', () => {
|
||
const testCases = [
|
||
// 自己的素材
|
||
{ userId: 'a', ownerId: 'a', scope: '', dept: '', expected: true },
|
||
// 同部门
|
||
{ userId: 'a', ownerId: 'b', scope: '部门A', dept: '部门A', expected: true },
|
||
// 子部门
|
||
{ userId: 'a', ownerId: 'b', scope: '部门A', dept: '部门A-子部门1', expected: true },
|
||
// 孙部门
|
||
{ userId: 'a', ownerId: 'b', scope: '部门A', dept: '部门A-子部门1-小组', expected: true },
|
||
// 其他部门
|
||
{ userId: 'a', ownerId: 'b', scope: '部门A', dept: '部门B', expected: false },
|
||
// 无管理范围
|
||
{ userId: 'a', ownerId: 'b', scope: '', dept: '部门A', expected: false },
|
||
]
|
||
|
||
testCases.forEach(({ userId, ownerId, scope, dept, expected }) => {
|
||
const result = canDeleteMaterial(userId, false, ownerId, scope, dept)
|
||
expect(result).toBe(expected)
|
||
})
|
||
})
|
||
})
|
||
|
||
/**
|
||
* Property 3: 超级管理员删除权限
|
||
* *For any* 超级管理员用户和任意素材,删除操作应始终成功
|
||
* **Feature: bugfix-dec-2025, Property 3: 超级管理员删除权限**
|
||
* **Validates: Requirements 3.3**
|
||
*/
|
||
describe('Property 3: 超级管理员删除权限', () => {
|
||
it('超级管理员可以删除自己上传的素材', () => {
|
||
const result = canDeleteMaterial('super-admin', true, 'super-admin')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('超级管理员可以删除其他管理员上传的素材', () => {
|
||
const result = canDeleteMaterial('super-admin', true, 'admin-1')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('超级管理员可以删除任意部门用户上传的素材', () => {
|
||
const result = canDeleteMaterial('super-admin', true, 'user-1', '', '任意部门')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
// 属性测试:对于任意超级管理员和任意素材
|
||
it('对于任意超级管理员,可以删除任意素材', () => {
|
||
const testCases = [
|
||
{ ownerId: 'super-admin', dept: '' },
|
||
{ ownerId: 'admin-1', dept: '设计部' },
|
||
{ ownerId: 'user-1', dept: '技术部' },
|
||
{ ownerId: 'user-2', dept: '市场部-销售组' },
|
||
{ ownerId: 'any-user', dept: '' },
|
||
]
|
||
|
||
testCases.forEach(({ ownerId, dept }) => {
|
||
const result = canDeleteMaterial('super-admin', true, ownerId, '', dept)
|
||
expect(result).toBe(true)
|
||
})
|
||
})
|
||
})
|
||
|
||
/**
|
||
* 边界情况测试
|
||
*/
|
||
describe('边界情况', () => {
|
||
it('空用户ID应该无法删除其他用户素材', () => {
|
||
const result = canDeleteMaterial('', false, 'some-owner')
|
||
expect(result).toBe(false)
|
||
})
|
||
|
||
it('用户ID和素材所有者ID都为空时应该可以删除(边界情况)', () => {
|
||
// 这是一个边界情况,空字符串相等
|
||
const result = canDeleteMaterial('', false, '')
|
||
expect(result).toBe(true)
|
||
})
|
||
|
||
it('部门名称包含连字符但不是子部门关系时不应该匹配', () => {
|
||
// "设计部-UI" 不是 "设计" 的子部门
|
||
const result = canDeleteMaterial('admin', false, 'user', '设计', '设计部-UI')
|
||
expect(result).toBe(false)
|
||
})
|
||
})
|
||
})
|