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 = {}) { 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() }) })