191 lines
6.5 KiB
TypeScript
191 lines
6.5 KiB
TypeScript
/** @jest-environment node */
|
|
|
|
import { getServerSession } from 'next-auth'
|
|
import { GET, POST } from '@/app/api/performance-survey/[...path]/route'
|
|
import {
|
|
getPerformanceSurveyDetail,
|
|
previewPerformanceSurveyEmployeeProfiles,
|
|
submitPerformanceSurveyResponse,
|
|
syncPerformanceSurveyEmployeeProfiles,
|
|
} from '@/lib/performance-survey-service'
|
|
import { verifyDingtalkPerformanceSessionToken } from '@/lib/dingtalk-performance-session'
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
jest.mock('@/lib/dingtalk-performance-session', () => ({
|
|
DINGTALK_PERFORMANCE_SESSION_HEADER: 'x-performance-token',
|
|
verifyDingtalkPerformanceSessionToken: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/performance-survey-service', () => ({
|
|
PerformanceSurveyApiError: class PerformanceSurveyApiError extends Error {
|
|
status: number
|
|
|
|
constructor(message: string, status = 400) {
|
|
super(message)
|
|
this.status = status
|
|
}
|
|
},
|
|
getPerformanceSurveyDetail: jest.fn(),
|
|
previewPerformanceSurveyEmployeeProfiles: jest.fn(),
|
|
submitPerformanceSurveyResponse: jest.fn(),
|
|
syncPerformanceSurveyEmployeeProfiles: jest.fn(),
|
|
}))
|
|
|
|
describe('performance survey employee sync route', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1' },
|
|
expires: '2099-01-01T00:00:00.000Z',
|
|
})
|
|
;(verifyDingtalkPerformanceSessionToken as jest.Mock).mockReturnValue(null)
|
|
})
|
|
|
|
test('钉钉评分凭证优先于浏览器普通会话并绑定指定评价对象', async () => {
|
|
;(verifyDingtalkPerformanceSessionToken as jest.Mock).mockReturnValue({
|
|
userId: 'external-1',
|
|
email: 'external@example.com',
|
|
name: '跨部门同事',
|
|
avatar: null,
|
|
role: 'USER',
|
|
department: '运营中心',
|
|
managedDepartment: null,
|
|
isSuperAdmin: false,
|
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
scope: { kind: 'assignment', surveyId: 'survey-1', targetId: 'target-1' },
|
|
})
|
|
;(getPerformanceSurveyDetail as jest.Mock).mockResolvedValue({ id: 'survey-1' })
|
|
|
|
const response = await GET(
|
|
new Request('https://example.test/api/performance-survey/surveys/survey-1?targetId=target-1', {
|
|
headers: { 'x-performance-token': 'signed-token' },
|
|
}),
|
|
{ params: { path: ['surveys', 'survey-1'] } },
|
|
)
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(getServerSession).not.toHaveBeenCalled()
|
|
expect(getPerformanceSurveyDetail).toHaveBeenCalledWith(
|
|
expect.objectContaining({ user: expect.objectContaining({ id: 'external-1' }) }),
|
|
'survey-1',
|
|
'target-1',
|
|
)
|
|
})
|
|
|
|
test('钉钉评分凭证不能读取通知以外的评价对象', async () => {
|
|
;(verifyDingtalkPerformanceSessionToken as jest.Mock).mockReturnValue({
|
|
userId: 'external-1',
|
|
email: 'external@example.com',
|
|
name: '跨部门同事',
|
|
role: 'USER',
|
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
scope: { kind: 'assignment', surveyId: 'survey-1', targetId: 'target-1' },
|
|
})
|
|
|
|
const response = await GET(
|
|
new Request('https://example.test/api/performance-survey/surveys/survey-1?targetId=target-other', {
|
|
headers: { 'x-performance-token': 'signed-token' },
|
|
}),
|
|
{ params: { path: ['surveys', 'survey-1'] } },
|
|
)
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(getPerformanceSurveyDetail).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('钉钉评分凭证提交时把通知评价对象传给服务层复核', async () => {
|
|
;(verifyDingtalkPerformanceSessionToken as jest.Mock).mockReturnValue({
|
|
userId: 'external-1',
|
|
email: 'external@example.com',
|
|
name: '跨部门同事',
|
|
role: 'USER',
|
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
scope: { kind: 'assignment', surveyId: 'survey-1', targetId: 'target-1' },
|
|
})
|
|
;(submitPerformanceSurveyResponse as jest.Mock).mockResolvedValue({ id: 'response-1' })
|
|
|
|
const response = await POST(
|
|
new Request('https://example.test/api/performance-survey/surveys/survey-1/responses', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-performance-token': 'signed-token',
|
|
},
|
|
body: JSON.stringify({ targetId: 'target-1', scores: {} }),
|
|
}),
|
|
{ params: { path: ['surveys', 'survey-1', 'responses'] } },
|
|
)
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(submitPerformanceSurveyResponse).toHaveBeenCalledWith(
|
|
expect.objectContaining({ user: expect.objectContaining({ id: 'external-1' }) }),
|
|
'survey-1',
|
|
{ targetId: 'target-1', scores: {} },
|
|
'target-1',
|
|
)
|
|
})
|
|
|
|
test('preview 子路径只执行只读预检', async () => {
|
|
;(previewPerformanceSurveyEmployeeProfiles as jest.Mock).mockResolvedValue({
|
|
mode: 'preview',
|
|
dryRun: true,
|
|
})
|
|
|
|
const response = await POST(
|
|
new Request('https://example.test/api/performance-survey/admin/sync-employees/preview', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ limit: 200 }),
|
|
}),
|
|
{ params: { path: ['admin', 'sync-employees', 'preview'] } },
|
|
)
|
|
const payload = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(payload).toEqual({
|
|
success: true,
|
|
data: { mode: 'preview', dryRun: true },
|
|
})
|
|
expect(previewPerformanceSurveyEmployeeProfiles).toHaveBeenCalledWith(
|
|
expect.objectContaining({ user: expect.objectContaining({ id: 'admin-1' }) }),
|
|
{ limit: 200 },
|
|
)
|
|
expect(syncPerformanceSurveyEmployeeProfiles).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('原同步路径继续执行正式同步,契约不变', async () => {
|
|
;(syncPerformanceSurveyEmployeeProfiles as jest.Mock).mockResolvedValue({
|
|
scannedAccounts: 10,
|
|
upserted: 8,
|
|
})
|
|
|
|
const response = await POST(
|
|
new Request('https://example.test/api/performance-survey/admin/sync-employees', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ limit: 3000 }),
|
|
}),
|
|
{ params: { path: ['admin', 'sync-employees'] } },
|
|
)
|
|
const payload = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(payload).toEqual({
|
|
success: true,
|
|
data: { scannedAccounts: 10, upserted: 8 },
|
|
})
|
|
expect(syncPerformanceSurveyEmployeeProfiles).toHaveBeenCalledWith(
|
|
expect.objectContaining({ user: expect.objectContaining({ id: 'admin-1' }) }),
|
|
{ limit: 3000 },
|
|
)
|
|
expect(previewPerformanceSurveyEmployeeProfiles).not.toHaveBeenCalled()
|
|
})
|
|
})
|