178 lines
8.1 KiB
TypeScript
178 lines
8.1 KiB
TypeScript
/** @jest-environment node */
|
||
|
||
import { getServerSession } from 'next-auth'
|
||
import { prisma } from '@/lib/prisma'
|
||
import { getAllMaterialTypes, isSuperAdmin } from '@/lib/department-permission'
|
||
import { savePermissionPackItems } from '@/lib/user-permission-inherit'
|
||
import { findConflictingDepartmentGroup, loadPermissionGroupDepartments } from '@/lib/permission-groups'
|
||
|
||
jest.mock('next-auth', () => ({
|
||
getServerSession: jest.fn(),
|
||
}))
|
||
|
||
jest.mock('@/lib/auth', () => ({
|
||
authOptions: {},
|
||
}))
|
||
|
||
jest.mock('@/lib/prisma', () => ({
|
||
prisma: {
|
||
materialPermissionPack: {
|
||
findUnique: jest.fn(),
|
||
findMany: jest.fn(),
|
||
},
|
||
organizationUnit: { findFirst: jest.fn(), findMany: jest.fn() },
|
||
performanceSurveyEmployeeProfile: { findMany: jest.fn() },
|
||
materialPermissionPackDepartment: { upsert: jest.fn(), deleteMany: jest.fn() },
|
||
},
|
||
}))
|
||
|
||
jest.mock('@/lib/department-permission', () => ({
|
||
getAllMaterialTypes: jest.fn(),
|
||
isSuperAdmin: jest.fn(),
|
||
}))
|
||
|
||
jest.mock('@/lib/user-permission-inherit', () => ({
|
||
ensureDefaultPermissionPacks: jest.fn(),
|
||
packItemsToMap: jest.fn((items: Array<{ materialType: string }>) => Object.fromEntries(items.map(({ materialType, ...flags }) => [materialType, flags]))),
|
||
savePermissionPackItems: jest.fn(),
|
||
}))
|
||
|
||
jest.mock('@/lib/permission-groups', () => ({
|
||
findConflictingDepartmentGroup: jest.fn(),
|
||
makePermissionGroupSlug: jest.fn(),
|
||
loadPermissionGroupDepartments: jest.fn(),
|
||
}))
|
||
|
||
const { GET, PUT } = require('@/app/api/organization/permission-groups/route') as typeof import('@/app/api/organization/permission-groups/route')
|
||
|
||
describe('/api/organization/permission-groups 保存权限', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks()
|
||
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'super-admin' } })
|
||
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
|
||
;(prisma.materialPermissionPack.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'visual-pack',
|
||
slug: 'visual',
|
||
name: '视觉',
|
||
})
|
||
})
|
||
|
||
test('任务详情私密权限仅任务详情可保存 true,其他类型会被后端压回 false', async () => {
|
||
const response = await PUT({
|
||
json: async () => ({
|
||
action: 'save-pack',
|
||
slug: 'visual',
|
||
permissions: [
|
||
{
|
||
materialType: '任务-详情',
|
||
canView: true,
|
||
canDownload: true,
|
||
canDownloadSource: true,
|
||
canEditAssets: true,
|
||
canAccessPrivate: true,
|
||
},
|
||
{
|
||
materialType: '3D场景',
|
||
canView: true,
|
||
canDownload: true,
|
||
canDownloadSource: false,
|
||
canAccessPrivate: true,
|
||
},
|
||
],
|
||
}),
|
||
} as never)
|
||
|
||
expect(response.status).toBe(200)
|
||
await expect(response.json()).resolves.toMatchObject({
|
||
ok: true,
|
||
permissions: {
|
||
'任务-详情': { canEditAssets: true, canAccessPrivate: true, canShare: true },
|
||
'3D场景': { canEditAssets: false, canAccessPrivate: false, canShare: true },
|
||
},
|
||
})
|
||
expect(savePermissionPackItems).toHaveBeenCalledWith('visual', [
|
||
expect.objectContaining({
|
||
materialType: '任务-详情',
|
||
canEditAssets: true,
|
||
canAccessPrivate: true,
|
||
}),
|
||
expect.objectContaining({
|
||
materialType: '3D场景',
|
||
canEditAssets: false,
|
||
canAccessPrivate: false,
|
||
}),
|
||
])
|
||
})
|
||
|
||
test('保存事务失败不返回已保存权限', async () => {
|
||
const errorLog = jest.spyOn(console, 'error').mockImplementation(() => undefined)
|
||
;(savePermissionPackItems as jest.Mock).mockRejectedValueOnce(new Error('transaction aborted'))
|
||
try {
|
||
const response = await PUT({ json: async () => ({ action: 'save-pack', slug: 'visual', permissions: [{ materialType: '任务-详情', canAccessPrivate: true }] }) } as never)
|
||
expect(response.status).toBe(500)
|
||
await expect(response.json()).resolves.toEqual({ error: '保存权限组失败' })
|
||
} finally { errorLog.mockRestore() }
|
||
})
|
||
|
||
test('普通用户不能保存权限,也不能获得保存结果', async () => {
|
||
;(isSuperAdmin as jest.Mock).mockResolvedValue(false)
|
||
const response = await PUT({ json: async () => ({ action: 'save-pack', slug: 'visual', permissions: [{ materialType: '任务-详情' }] }) } as never)
|
||
expect(response.status).toBe(403)
|
||
expect(savePermissionPackItems).not.toHaveBeenCalled()
|
||
})
|
||
|
||
test('组页面展示数据库当前部门;未确认身份的历史关联明确失效', async () => {
|
||
;(prisma.materialPermissionPack.findMany as jest.Mock).mockResolvedValue([{ id: 'visual-pack', slug: 'visual', name: '管理员自定义名', items: [], inheritors: [] }])
|
||
;(prisma.organizationUnit.findMany as jest.Mock).mockResolvedValue([{ id: 'stable-dept', name: '新部门', path: '公司/新部门', level: 1, parentId: null, sourceDeptId: 42 }])
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([])
|
||
;(getAllMaterialTypes as jest.Mock).mockResolvedValue(['任务-详情'])
|
||
;(loadPermissionGroupDepartments as jest.Mock).mockResolvedValue([
|
||
{ id: 'known', packId: 'visual-pack', departmentPath: '公司/旧部门', organizationUnitId: 'stable-dept', organizationUnit: { id: 'stable-dept', path: '公司/新部门', name: '新部门', isActive: true } },
|
||
{ id: 'unknown', packId: 'visual-pack', departmentPath: '公司/已消失部门', organizationUnitId: null, organizationUnit: null },
|
||
])
|
||
const response = await GET()
|
||
expect(response.status).toBe(200)
|
||
const body = await response.json()
|
||
expect(body.groups[0].name).toBe('管理员自定义名')
|
||
expect(body.groups[0].departments).toMatchObject([
|
||
{ id: 'known', path: '公司/新部门', name: '新部门', status: 'active' },
|
||
{ id: 'unknown', path: '公司/已消失部门', status: 'invalid' },
|
||
])
|
||
expect(body.tree).toHaveLength(1)
|
||
expect(body.tree[0].path).toBe('公司/新部门')
|
||
})
|
||
|
||
test('按稳定部门 ID 加组,忽略客户端的旧名称路径', async () => {
|
||
;(prisma.organizationUnit.findFirst as jest.Mock).mockResolvedValue({ path: '公司/新部门' })
|
||
;(findConflictingDepartmentGroup as jest.Mock).mockResolvedValue(null)
|
||
;(prisma.materialPermissionPackDepartment.upsert as jest.Mock).mockResolvedValue({ packId: 'visual-pack' })
|
||
const response = await PUT({ json: async () => ({ action: 'add-department', slug: 'visual', organizationUnitId: 'stable-dept', path: '公司/旧部门' }) } as never)
|
||
expect(response.status).toBe(200)
|
||
expect(prisma.materialPermissionPackDepartment.upsert).toHaveBeenCalledWith(expect.objectContaining({
|
||
where: { organizationUnitId: 'stable-dept' },
|
||
create: { packId: 'visual-pack', organizationUnitId: 'stable-dept', departmentPath: '公司/新部门' },
|
||
}))
|
||
})
|
||
|
||
test('失效关联可以精确移除,并限定在当前权限组内', async () => {
|
||
const response = await PUT({ json: async () => ({ action: 'remove-department', slug: 'visual', associationId: 'stale-row' }) } as never)
|
||
expect(response.status).toBe(200)
|
||
expect(prisma.materialPermissionPackDepartment.deleteMany).toHaveBeenCalledWith({ where: { packId: 'visual-pack', id: 'stale-row' } })
|
||
expect(prisma.organizationUnit.findFirst).not.toHaveBeenCalled()
|
||
})
|
||
|
||
test('并发加组不能把其他组的绑定当作本组保存成功', async () => {
|
||
;(prisma.organizationUnit.findFirst as jest.Mock).mockResolvedValue({ path: '公司/部门' })
|
||
;(findConflictingDepartmentGroup as jest.Mock).mockResolvedValue(null)
|
||
;(prisma.materialPermissionPackDepartment.upsert as jest.Mock).mockResolvedValue({ packId: 'other-pack' })
|
||
const response = await PUT({ json: async () => ({ action: 'add-department', slug: 'visual', organizationUnitId: 'stable-dept' }) } as never)
|
||
expect(response.status).toBe(409)
|
||
})
|
||
|
||
test('旧页面只发送部门名称时要求刷新,不能凭名称授予权限', async () => {
|
||
const response = await PUT({ json: async () => ({ action: 'add-department', slug: 'visual', path: '公司/部门' }) } as never)
|
||
expect(response.status).toBe(400)
|
||
expect(prisma.materialPermissionPackDepartment.upsert).not.toHaveBeenCalled()
|
||
})
|
||
})
|