231 lines
6.9 KiB
TypeScript
231 lines
6.9 KiB
TypeScript
import { getServerSession } from 'next-auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
organizationUnit: {
|
|
findFirst: jest.fn(),
|
|
findMany: jest.fn(),
|
|
},
|
|
performanceSurveyEmployeeProfile: {
|
|
findMany: jest.fn(),
|
|
},
|
|
user: {
|
|
findMany: jest.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
jest.mock('@/lib/department-permission', () => ({
|
|
isAdmin: jest.fn((role: string) => ['ADMIN', 'SYSTEM_ADMIN', 'DEPT_ADMIN'].includes(role)),
|
|
isSystemAdmin: jest.fn((role: string) => ['ADMIN', 'SYSTEM_ADMIN'].includes(role)),
|
|
isDepartmentAdmin: jest.fn((role: string) => role === 'DEPT_ADMIN'),
|
|
}))
|
|
|
|
const { TextDecoder, TextEncoder } = require('util')
|
|
const { clearImmediate, setImmediate } = require('timers')
|
|
Object.assign(globalThis, { TextDecoder, TextEncoder, clearImmediate, setImmediate })
|
|
const edgePrimitives = require('next/dist/compiled/@edge-runtime/primitives')
|
|
Object.assign(globalThis, {
|
|
Request: edgePrimitives.Request,
|
|
Response: edgePrimitives.Response,
|
|
Headers: edgePrimitives.Headers,
|
|
})
|
|
|
|
const { GET } = require('@/app/api/organization/route') as typeof import('@/app/api/organization/route')
|
|
|
|
const companyPath = '江苏简尚品牌管理有限公司'
|
|
const brandPath = `${companyPath}/品牌中心`
|
|
const supportPath = `${companyPath}/业务支持`
|
|
|
|
const units = [
|
|
{
|
|
id: 'company',
|
|
name: '江苏简尚品牌管理有限公司',
|
|
path: companyPath,
|
|
level: 0,
|
|
parentId: null,
|
|
sourceDeptId: 1,
|
|
},
|
|
{
|
|
id: 'brand',
|
|
name: '品牌中心',
|
|
path: brandPath,
|
|
level: 1,
|
|
parentId: 'company',
|
|
sourceDeptId: 2,
|
|
},
|
|
{
|
|
id: 'support',
|
|
name: '业务支持',
|
|
path: supportPath,
|
|
level: 1,
|
|
parentId: 'company',
|
|
sourceDeptId: 3,
|
|
},
|
|
]
|
|
|
|
function createProfile(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: 'profile-brand',
|
|
displayName: '花名册员工',
|
|
realName: '花名册实名',
|
|
jobNumber: 'HR001',
|
|
departmentPath: brandPath,
|
|
departmentName: '品牌中心',
|
|
positionTitle: '设计师',
|
|
positionLevel: 'P2',
|
|
levelType: 'P',
|
|
managerName: '负责人',
|
|
isLeadership: false,
|
|
employmentState: 'ACTIVE',
|
|
sourcePayload: null,
|
|
departmentMemberships: [],
|
|
userBindings: [],
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function request(query = '') {
|
|
return { url: `https://sc.jiansun.vip/api/organization${query}` } as any
|
|
}
|
|
|
|
describe('/api/organization HR 花名册口径', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1', role: 'SYSTEM_ADMIN' },
|
|
})
|
|
;(prisma.organizationUnit.findMany as jest.Mock).mockResolvedValue(units)
|
|
;(prisma.organizationUnit.findFirst as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.user.findMany as jest.Mock).mockResolvedValue([])
|
|
})
|
|
|
|
test('未注册素材库账号的 HR 员工仍进入组织树和人员明细', async () => {
|
|
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([
|
|
createProfile(),
|
|
createProfile({
|
|
id: 'profile-bound',
|
|
displayName: '已绑定员工',
|
|
realName: 'HR 权威实名',
|
|
jobNumber: 'HR002',
|
|
userBindings: [
|
|
{
|
|
userId: 'user-2',
|
|
user: { username: 'material-user', isActive: true },
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
|
|
const response = await GET(request())
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body.summary).toEqual(expect.objectContaining({
|
|
totalEmployees: 2,
|
|
activeEmployees: 2,
|
|
visibleCount: 2,
|
|
scope: 'all',
|
|
}))
|
|
expect(body.employees).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({
|
|
employeeProfileId: 'profile-brand',
|
|
userId: null,
|
|
username: null,
|
|
realName: '花名册实名',
|
|
accountIsActive: null,
|
|
}),
|
|
expect.objectContaining({
|
|
employeeProfileId: 'profile-bound',
|
|
userId: 'user-2',
|
|
username: 'material-user',
|
|
realName: 'HR 权威实名',
|
|
accountIsActive: true,
|
|
}),
|
|
]))
|
|
expect(body.tree[0]).toEqual(expect.objectContaining({ count: 2 }))
|
|
})
|
|
|
|
test('离职人员默认隐藏,明确选择后才显示', async () => {
|
|
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([
|
|
createProfile(),
|
|
createProfile({
|
|
id: 'profile-resigned',
|
|
displayName: '离职员工',
|
|
jobNumber: 'HR003',
|
|
employmentState: 'RESIGNED',
|
|
}),
|
|
])
|
|
|
|
const defaultResponse = await GET(request())
|
|
const defaultBody = await defaultResponse.json()
|
|
expect(defaultBody.employees).toHaveLength(1)
|
|
expect(defaultBody.summary).toEqual(expect.objectContaining({
|
|
totalEmployees: 2,
|
|
activeEmployees: 1,
|
|
inactiveEmployees: 1,
|
|
visibleCount: 1,
|
|
}))
|
|
|
|
const allResponse = await GET(request('?includeInactive=true'))
|
|
const allBody = await allResponse.json()
|
|
expect(allBody.employees).toHaveLength(2)
|
|
expect(allBody.summary.visibleCount).toBe(2)
|
|
})
|
|
|
|
test('部门管理员只获得本部门 HR 人员与本部门统计', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'dept-1', role: 'DEPT_ADMIN', managedDepartment: '品牌中心' },
|
|
})
|
|
;(prisma.organizationUnit.findFirst as jest.Mock).mockResolvedValue({ path: brandPath })
|
|
;(prisma.performanceSurveyEmployeeProfile.findMany as jest.Mock).mockResolvedValue([
|
|
createProfile(),
|
|
createProfile({
|
|
id: 'profile-support',
|
|
displayName: '业务支持员工',
|
|
jobNumber: 'HR004',
|
|
departmentPath: supportPath,
|
|
departmentName: '业务支持',
|
|
}),
|
|
])
|
|
|
|
const response = await GET(request())
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body.employees).toHaveLength(1)
|
|
expect(body.employees[0].employeeProfileId).toBe('profile-brand')
|
|
expect(body.summary).toEqual(expect.objectContaining({
|
|
totalEmployees: 1,
|
|
activeEmployees: 1,
|
|
visibleCount: 1,
|
|
scope: brandPath,
|
|
}))
|
|
expect(body.unbound).toEqual([])
|
|
expect(prisma.user.findMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('部门管理员缺少有效管理范围时拒绝访问而不是回退全公司', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'dept-1', role: 'DEPT_ADMIN', managedDepartment: '不存在的部门' },
|
|
})
|
|
;(prisma.organizationUnit.findFirst as jest.Mock).mockResolvedValue(null)
|
|
|
|
const response = await GET(request())
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(body.error).toBe('未找到可访问部门范围')
|
|
expect(prisma.performanceSurveyEmployeeProfile.findMany).not.toHaveBeenCalled()
|
|
})
|
|
})
|