141 lines
7.3 KiB
TypeScript
141 lines
7.3 KiB
TypeScript
/** @jest-environment node */
|
||
import { prisma } from '@/lib/prisma'
|
||
import { resolveEmployeeDirectManagerProfile } from '@/lib/employee-manager'
|
||
|
||
jest.mock('@/lib/prisma', () => ({ prisma: {
|
||
performanceSurveyEmployeeProfile: { findMany: jest.fn() },
|
||
performanceSurveyEmployeeDepartmentMembership: { findMany: jest.fn() },
|
||
organizationUnit: { findUnique: jest.fn(), findMany: jest.fn() },
|
||
} }))
|
||
const subject = {
|
||
employeeProfileId: 'employee-1',
|
||
displayName: '测试人员',
|
||
dingtalkUserId: 'employee-ding',
|
||
jobNumber: 'P001',
|
||
}
|
||
const manager = {
|
||
id: 'manager-1',
|
||
dingtalkUserId: 'manager-ding',
|
||
jobNumber: 'M001',
|
||
departmentMemberships: [{ sourceDeptId: '123', isPrimary: true }],
|
||
}
|
||
beforeEach(() => {
|
||
jest.clearAllMocks()
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([manager])
|
||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock).mockResolvedValue([{ sourceDeptId: '123', departmentPath: '集团/中心/部门' }])
|
||
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue(null)
|
||
;(prisma.organizationUnit.findMany as jest.Mock).mockResolvedValue([])
|
||
})
|
||
test('主管钉钉ID和工号均存在时必须同时匹配,不能降级组织推断', async () => {
|
||
expect(await resolveEmployeeDirectManagerProfile({ ...subject, managerDingtalkUserId: 'manager-ding', managerJobNumber: 'M001' })).toEqual(manager)
|
||
expect(prisma.performanceSurveyEmployeeProfile.findMany).toHaveBeenCalledWith({ where: { AND: [{ employmentState: 'ACTIVE' }, { id: { not: 'employee-1' } }, { dingtalkUserId: 'manager-ding', jobNumber: 'M001' }] }, take: 2 })
|
||
expect(prisma.performanceSurveyEmployeeDepartmentMembership.findMany).not.toHaveBeenCalled()
|
||
})
|
||
test('只有主管工号也支持精确解析', async () => {
|
||
await resolveEmployeeDirectManagerProfile({ ...subject, managerJobNumber: 'M001' })
|
||
expect(prisma.performanceSurveyEmployeeProfile.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { AND: [
|
||
{ employmentState: 'ACTIVE' },
|
||
{ id: { not: 'employee-1' } },
|
||
{ jobNumber: 'M001' },
|
||
] } }))
|
||
})
|
||
test('主管唯一值缺失时,仅按同一主组织唯一在职管理人员解析', async () => {
|
||
expect(await resolveEmployeeDirectManagerProfile(subject)).toEqual(manager)
|
||
expect(prisma.performanceSurveyEmployeeProfile.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { AND: expect.arrayContaining([
|
||
{ employmentState: 'ACTIVE' }, { id: { not: 'employee-1' } }, { departmentMemberships: { some: { sourceDeptId: { in: ['123'] } } } },
|
||
]) } }))
|
||
const query = (prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mock.calls[0][0]
|
||
expect(query.where.AND).toContainEqual({ OR: [
|
||
{ levelType: { equals: 'M', mode: 'insensitive' } },
|
||
{ positionLevel: { startsWith: 'M', mode: 'insensitive' } },
|
||
] })
|
||
expect(JSON.stringify(query)).not.toContain('isLeadership')
|
||
})
|
||
test('当前部门没有M级人员时,沿真实组织树采用最近上级组织的唯一M级人员', async () => {
|
||
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue({ path: '集团/中心/部门' })
|
||
;(prisma.organizationUnit.findMany as jest.Mock).mockResolvedValue([
|
||
{ sourceDeptId: 123, path: '集团/中心/部门' },
|
||
{ sourceDeptId: 12, path: '集团/中心' },
|
||
{ sourceDeptId: 1, path: '集团' },
|
||
])
|
||
const parentManager = {
|
||
...manager,
|
||
id: 'parent-manager',
|
||
departmentMemberships: [{ sourceDeptId: '12', isPrimary: true }],
|
||
}
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([parentManager])
|
||
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).resolves.toEqual(parentManager)
|
||
const query = (prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mock.calls[0][0]
|
||
expect(query.where.AND).toContainEqual({
|
||
departmentMemberships: {
|
||
some: { sourceDeptId: { in: ['123', '12'] } },
|
||
},
|
||
})
|
||
})
|
||
test('同层级有主部门M和兼任M时优先主部门M', async () => {
|
||
const primaryManager = {
|
||
...manager,
|
||
id: 'primary-manager',
|
||
departmentMemberships: [{ sourceDeptId: '123', isPrimary: true }],
|
||
}
|
||
const concurrentManager = {
|
||
...manager,
|
||
id: 'concurrent-manager',
|
||
departmentMemberships: [{ sourceDeptId: '123', isPrimary: false }],
|
||
}
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([
|
||
concurrentManager,
|
||
primaryManager,
|
||
])
|
||
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).resolves.toEqual(primaryManager)
|
||
})
|
||
test('同层级没有主部门M但只有一个兼任M时采用该兼任M', async () => {
|
||
const concurrentManager = {
|
||
...manager,
|
||
id: 'concurrent-manager',
|
||
departmentMemberships: [{ sourceDeptId: '123', isPrimary: false }],
|
||
}
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([concurrentManager])
|
||
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).resolves.toEqual(concurrentManager)
|
||
})
|
||
test('组织回退不会跨到公司根节点猜测主管', async () => {
|
||
;(prisma.organizationUnit.findUnique as jest.Mock).mockResolvedValue({ path: '集团/中心/部门' })
|
||
;(prisma.organizationUnit.findMany as jest.Mock).mockResolvedValue([
|
||
{ sourceDeptId: 123, path: '集团/中心/部门' },
|
||
{ sourceDeptId: 12, path: '集团/中心' },
|
||
{ sourceDeptId: 1, path: '集团' },
|
||
])
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([{
|
||
...manager,
|
||
id: 'root-manager',
|
||
departmentMemberships: [{ sourceDeptId: '1', isPrimary: true }],
|
||
}])
|
||
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).rejects.toThrow('没有可用的M级主管')
|
||
})
|
||
test.each([
|
||
{ managerDingtalkUserId: 'employee-ding' },
|
||
{ managerJobNumber: 'P001' },
|
||
])('明确主管身份指向本人时直接拒绝且不做组织猜测', async (managerIdentity) => {
|
||
await expect(resolveEmployeeDirectManagerProfile({ ...subject, ...managerIdentity })).rejects.toThrow('不能是本人')
|
||
expect(prisma.performanceSurveyEmployeeProfile.findMany).not.toHaveBeenCalled()
|
||
expect(prisma.performanceSurveyEmployeeDepartmentMembership.findMany).not.toHaveBeenCalled()
|
||
})
|
||
test.each([{ rows: [] }, { rows: [{ id: 'm1' }, { id: 'm2' }] }])('明确身份无结果或冲突时禁止组织降级', async ({ rows }) => {
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue(rows)
|
||
await expect(resolveEmployeeDirectManagerProfile({ ...subject, managerDingtalkUserId: 'bad' })).rejects.toThrow('无法唯一匹配')
|
||
expect(prisma.performanceSurveyEmployeeDepartmentMembership.findMany).not.toHaveBeenCalled()
|
||
})
|
||
test.each([{ memberships: [] }, { memberships: [{ sourceDeptId:'1' }, { sourceDeptId:'2' }] }])('主组织缺失或冲突时拒绝', async ({ memberships }) => {
|
||
;(prisma.performanceSurveyEmployeeDepartmentMembership.findMany as jest.Mock).mockResolvedValue(memberships)
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).rejects.toThrow('主组织')
|
||
expect(prisma.performanceSurveyEmployeeProfile.findMany).not.toHaveBeenCalled()
|
||
})
|
||
test.each([{ rows: [] }, { rows: [{ id: 'm1' }, { id: 'm2' }] }])('管理候选缺失或多于一人时拒绝', async ({ rows }) => {
|
||
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue(rows)
|
||
await expect(resolveEmployeeDirectManagerProfile(subject)).rejects.toThrow('主管')
|
||
})
|