gongxiangp2/lib/__tests__/employee-manager.test.ts

141 lines
7.3 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 { 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('主管')
})