gongxiangp2/lib/__tests__/material-access-scope.test.ts

119 lines
6.9 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 { prisma } from '@/lib/prisma'
import { getEmployeeByUserId, getEmployeeOrganizationPathsByUserId } from '@/lib/employee-directory'
import { canUserAccessPrivateTaskDetail } from '@/lib/user-permission-inherit'
import { canAccessOperationsGMaterials, resolveMaterialUploadAccessScope, OPERATIONS_G_SOURCE_DEPT_ID } from '@/lib/material-organization-access'
import { canAccessMaterialPrivacy, buildMaterialPrivacyWhere } from '@/lib/task-detail-privacy'
import { buildMaterialWhereFromSearchParams } from '@/lib/material-query'
import { normalizeTaskDetailPrivacyFields } from '@/lib/task-detail-privacy-fields'
jest.mock('@/lib/prisma', () => ({ prisma: { organizationUnit: { findUnique: jest.fn() } } }))
jest.mock('@/lib/employee-directory', () => ({
getEmployeeByUserId: jest.fn(), getEmployeeOrganizationPathsByUserId: jest.fn(),
}))
jest.mock('@/lib/user-permission-inherit', () => ({ canUserAccessPrivateTaskDetail: jest.fn() }))
jest.mock('@/lib/department-permission', () => ({
isSuperAdmin: jest.fn(), isSystemAdmin: jest.fn(), getUserViewableMaterialTypes: jest.fn(),
}))
const root = '江苏简尚品牌管理有限公司/运营中心G'
const exclusive = { accessScope: 'operations_g', userId: 'uploader', taskDetailRequesterUserId: 'requester', taskDetailPrivate: false }
beforeEach(() => {
jest.clearAllMocks()
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue({ path: root, isActive: true })
;(getEmployeeByUserId as jest.Mock).mockResolvedValue({ employmentState: 'ACTIVE' })
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([`${root}/运营七部(HZ)/设计组`])
;(canUserAccessPrivateTaskDetail as jest.Mock).mockResolvedValue(false)
})
test.each([root, `${root}/运营七部(HZ)/设计组`, `${root}/运营一部(CZ)/京东一组`])(
'整个中心及其下属部门可以访问:%s', async (memberPath) => {
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([memberPath])
expect(await canAccessOperationsGMaterials('member')).toBe(true)
expect(prisma.organizationUnit.findUnique).toHaveBeenCalledWith({
where: { sourceDeptId: OPERATIONS_G_SOURCE_DEPT_ID }, select: { path: true, isActive: true },
})
},
)
test.each([{ paths: [] }, { paths: ['江苏简尚品牌管理有限公司/运营中心A'] }, { paths: [`${root}二部/设计组`] }])(
'其他中心、无绑定及相似名称不能获得专属权限:%j', async ({ paths }) => {
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue(paths)
expect(await canAccessOperationsGMaterials('other')).toBe(false)
},
)
test('离职成员和失效组织均失去专属资格', async () => {
;(getEmployeeByUserId as jest.Mock).mockResolvedValue({ employmentState: 'RESIGNED' })
expect(await canAccessOperationsGMaterials('member')).toBe(false)
;(getEmployeeByUserId as jest.Mock).mockResolvedValue({ employmentState: 'ACTIVE' })
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue({ path: root, isActive: false })
expect(await canAccessOperationsGMaterials('member')).toBe(false)
})
test('保留现有组织成员口径,已绑定成员的未知在职状态不等同于离职', async () => {
;(getEmployeeByUserId as jest.Mock).mockResolvedValue({ employmentState: 'UNKNOWN' })
expect(await canAccessOperationsGMaterials('member')).toBe(true)
})
test('稳定部门编号绑定当前组织路径,组织改名后仍使用当前边界', async () => {
const renamed = '江苏简尚品牌管理有限公司/运营中心G新名称'
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue({ path: renamed, isActive: true })
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([`${renamed}/设计组`])
expect(await canAccessOperationsGMaterials('member')).toBe(true)
})
test('专属素材在中心内共享,无需个人私密授权', async () => {
expect(await canAccessMaterialPrivacy(exclusive, 'any-g-member')).toBe(true)
expect(canUserAccessPrivateTaskDetail).not.toHaveBeenCalled()
})
test.each(['requester', 'uploader', 'super-admin'])('中心外不能凭需求人、上传者或管理员身份访问:%s', async (userId) => {
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue(['江苏简尚品牌管理有限公司/品牌中心'])
;(canUserAccessPrivateTaskDetail as jest.Mock).mockResolvedValue(true)
expect(await canAccessMaterialPrivacy(exclusive, userId)).toBe(false)
expect(canUserAccessPrivateTaskDetail).not.toHaveBeenCalled()
})
test('匿名访问专属素材被拒绝,通用素材保留原有准入', async () => {
expect(await canAccessMaterialPrivacy(exclusive, null)).toBe(false)
expect(await canAccessMaterialPrivacy({ accessScope: 'general' }, null)).toBe(true)
})
test('上传校验拒绝非法范围和中心外专属上传', async () => {
await expect(resolveMaterialUploadAccessScope('any-department', 'member')).rejects.toMatchObject({ status: 400 })
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([])
await expect(resolveMaterialUploadAccessScope('operations_g', 'other')).rejects.toMatchObject({ status: 403 })
expect(await resolveMaterialUploadAccessScope(undefined, 'other')).toBe('general')
})
test('专属素材不能叠加个人私密,不受新品或优化限制', () => {
expect(() => normalizeTaskDetailPrivacyFields({ accessScope: 'operations_g', taskDetailPrivate: true })).toThrow('不能设为个人私密')
for (const taskDetailDemandType of ['新品', '优化']) {
expect(normalizeTaskDetailPrivacyFields({ accessScope: 'operations_g', taskDetailDemandType }).taskDetailPrivate).toBe(false)
}
})
test('中心内可浏览各类型专属素材,通用素材继续遵守原类型权限', async () => {
const result = await buildMaterialWhereFromSearchParams(new URLSearchParams('materialType=视频&search=SPU'), { id: 'member' }, {
context: { canAccessDeleted: false, expandedViewableTypes: ['任务-详情'], isSuperAdmin: false,
canAccessOperationsGMaterials: true, searchOrConditions: [{ productModel: { contains: 'SPU' } }] },
})
expect(result.empty).toBe(false)
expect(result.where.AND[0].OR).toEqual(expect.arrayContaining([
{ accessScope: 'general', taskDetailPrivate: false, materialType: { in: ['任务-详情'] } },
{ accessScope: 'operations_g' },
]))
expect(result.where.OR).toEqual([{ productModel: { contains: 'SPU' } }])
})
test('列表、收藏、月份计数的共用过滤不向中心外泄露专属条目', async () => {
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([])
const outside = await buildMaterialPrivacyWhere('other')
expect(outside.OR.every((branch: any) => branch.accessScope === 'general')).toBe(true)
;(getEmployeeOrganizationPathsByUserId as jest.Mock).mockResolvedValue([root])
expect((await buildMaterialPrivacyWhere('member')).OR).toContainEqual({ accessScope: 'operations_g' })
})