178 lines
8.1 KiB
TypeScript
Raw 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.

/** @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()
})
})