547 lines
20 KiB
TypeScript
547 lines
20 KiB
TypeScript
/** @jest-environment node */
|
||
|
||
import {
|
||
processPerformanceSurveyNotification,
|
||
recoverMissingSurveyAssignmentNotifications,
|
||
queueSurveyAssignmentNotifications,
|
||
queueSurveyTargetConfirmationNotifications,
|
||
recoverDuePerformanceSurveyNotifications,
|
||
refreshPerformanceSurveyNotificationReadStatuses,
|
||
retryPerformanceSurveyNotification,
|
||
} from '@/lib/performance-survey-notifications'
|
||
import { prisma } from '@/lib/prisma'
|
||
import {
|
||
getDingtalkRobotOtoReadResult,
|
||
getDingtalkWorkNoticeSendResult,
|
||
sendDingtalkRobotOtoMessage,
|
||
sendDingtalkWorkNotice,
|
||
} from '@/lib/dingtalk-client'
|
||
|
||
jest.mock('@/lib/prisma', () => ({
|
||
prisma: {
|
||
performanceSurvey: {
|
||
findUnique: jest.fn(),
|
||
},
|
||
performanceSurveyNotification: {
|
||
findUnique: jest.fn(),
|
||
create: jest.fn(),
|
||
update: jest.fn(),
|
||
updateMany: jest.fn(),
|
||
findMany: jest.fn(),
|
||
},
|
||
performanceSurveyAssignment: {
|
||
findMany: jest.fn(),
|
||
},
|
||
performanceSurveyEmployeeProfile: {
|
||
findUnique: jest.fn(),
|
||
},
|
||
user: {
|
||
findUnique: jest.fn(),
|
||
findFirst: jest.fn(),
|
||
},
|
||
},
|
||
}))
|
||
|
||
jest.mock('@/lib/dingtalk-client', () => ({
|
||
getDingtalkPerformanceSurveyNotificationChannelForUser: jest.fn((userId: string) => {
|
||
if (process.env.DINGTALK_PERFORMANCE_SURVEY_NOTIFICATION_CHANNEL !== 'robot_oto') {
|
||
return 'work_notice'
|
||
}
|
||
const allowlist = String(process.env.DINGTALK_ROBOT_OTO_TEST_USER_IDS || '')
|
||
.split(',')
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
return allowlist.length === 0 || allowlist.includes(userId) ? 'robot_oto' : 'work_notice'
|
||
}),
|
||
sendDingtalkRobotOtoMessage: jest.fn(),
|
||
getDingtalkRobotOtoReadResult: jest.fn(),
|
||
sendDingtalkWorkNotice: jest.fn(),
|
||
getDingtalkWorkNoticeSendResult: jest.fn(),
|
||
}))
|
||
|
||
jest.mock('@/lib/public-app-url', () => ({
|
||
getPublicAppOrigin: jest.fn(() => 'https://sc.jiansun.vip'),
|
||
}))
|
||
|
||
function notification(overrides: Record<string, unknown> = {}) {
|
||
return {
|
||
id: 'notification-1',
|
||
dingtalkUserId: null,
|
||
recipientUserId: null,
|
||
recipientEmployeeId: null,
|
||
title: '你有新的考核调研待填写',
|
||
content: '请填写考核调研',
|
||
actionUrl: 'https://sc.jiansun.vip/performance-survey/survey/survey-1',
|
||
eventType: 'survey.assignment.created',
|
||
status: 'pending',
|
||
attemptCount: 0,
|
||
maxAttempts: 5,
|
||
...overrides,
|
||
}
|
||
}
|
||
|
||
function buildAssignment(role: 'self' | 'visual_final' | 'manager', status: 'pending' | 'submitted', id: string) {
|
||
return {
|
||
id,
|
||
status,
|
||
targetId: 'target-1',
|
||
evaluatorEmployeeId: `employee-${role}`,
|
||
evaluatorUserId: null,
|
||
evaluatorDingtalkUserId: `ding-${role}`,
|
||
evaluatorName: role,
|
||
evaluatorSnapshot: { performanceSurveyWorkflowRole: role },
|
||
evaluatorEmployee: null,
|
||
target: { id: 'target-1', targetName: '视觉同事' },
|
||
}
|
||
}
|
||
|
||
describe('performance survey notifications', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks()
|
||
jest.useFakeTimers()
|
||
delete process.env.DINGTALK_PERFORMANCE_SURVEY_NOTIFICATION_CHANNEL
|
||
delete process.env.DINGTALK_ROBOT_OTO_TEST_USER_IDS
|
||
delete process.env.YYD_NOTIFICATION_CENTER_TEST_USER_IDS
|
||
;(prisma.performanceSurveyNotification.update as jest.Mock).mockResolvedValue({})
|
||
;(prisma.performanceSurveyNotification.create as jest.Mock).mockResolvedValue({ id: 'notification-confirmation' })
|
||
;(prisma.performanceSurveyNotification.updateMany as jest.Mock).mockResolvedValue({ count: 0 })
|
||
;(prisma.performanceSurveyNotification.findMany as jest.Mock).mockResolvedValue([])
|
||
;(prisma.performanceSurveyAssignment.findMany as jest.Mock).mockResolvedValue([])
|
||
;(prisma.performanceSurveyEmployeeProfile.findUnique as jest.Mock).mockResolvedValue(null)
|
||
;(prisma.user.findUnique as jest.Mock).mockResolvedValue(null)
|
||
;(prisma.user.findFirst as jest.Mock).mockResolvedValue(null)
|
||
;(sendDingtalkWorkNotice as jest.Mock).mockResolvedValue({
|
||
taskId: 'task-1',
|
||
raw: { errcode: 0, task_id: 'task-1' },
|
||
})
|
||
;(getDingtalkWorkNoticeSendResult as jest.Mock).mockResolvedValue({
|
||
readUserIds: [],
|
||
unreadUserIds: [],
|
||
failedUserIds: [],
|
||
invalidUserIds: [],
|
||
forbiddenUserIds: [],
|
||
raw: { errcode: 0 },
|
||
})
|
||
;(sendDingtalkRobotOtoMessage as jest.Mock).mockResolvedValue({
|
||
processQueryKey: 'query-1',
|
||
invalidUserIds: [],
|
||
filteredUserIds: [],
|
||
flowControlledUserIds: [],
|
||
raw: { processQueryKey: 'query-1' },
|
||
})
|
||
;(getDingtalkRobotOtoReadResult as jest.Mock).mockResolvedValue({
|
||
readUserIds: [],
|
||
unreadUserIds: [],
|
||
sendStatus: 'success',
|
||
deliveryFailed: false,
|
||
raw: { sendStatus: 'success' },
|
||
})
|
||
})
|
||
|
||
afterEach(() => {
|
||
jest.useRealTimers()
|
||
})
|
||
|
||
test('发送前会从素材库用户绑定补齐钉钉 userId', async () => {
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(
|
||
notification({ recipientUserId: 'user-1' }),
|
||
)
|
||
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({ dingtalkUserId: 'ding-user-1' })
|
||
|
||
const result = await processPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(result).toEqual({ success: true, status: 'succeeded' })
|
||
expect(sendDingtalkWorkNotice).toHaveBeenCalledWith({
|
||
userIds: ['ding-user-1'],
|
||
title: '你有新的考核调研待填写',
|
||
text: '请填写考核调研',
|
||
actionUrl: 'https://sc.jiansun.vip/performance-survey/survey/survey-1',
|
||
actionTitle: '立即填写',
|
||
})
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({ dingtalkUserId: 'ding-user-1' }),
|
||
})
|
||
})
|
||
|
||
test('人员档案缺 userId 时会通过邮箱匹配素材库用户', async () => {
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(
|
||
notification({ recipientEmployeeId: 'employee-1' }),
|
||
)
|
||
;(prisma.performanceSurveyEmployeeProfile.findUnique as jest.Mock).mockResolvedValue({
|
||
dingtalkUserId: null,
|
||
dingEmail: 'user@example.com',
|
||
})
|
||
;(prisma.user.findFirst as jest.Mock).mockResolvedValue({ dingtalkUserId: 'ding-user-2' })
|
||
|
||
await processPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(sendDingtalkWorkNotice).toHaveBeenCalledWith(expect.objectContaining({
|
||
userIds: ['ding-user-2'],
|
||
}))
|
||
})
|
||
|
||
test('配置机器人单聊后会记录 processQueryKey 和 providerType', async () => {
|
||
process.env.DINGTALK_PERFORMANCE_SURVEY_NOTIFICATION_CHANNEL = 'robot_oto'
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(
|
||
notification({ dingtalkUserId: 'ding-user-1' }),
|
||
)
|
||
|
||
const result = await processPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(result).toEqual({ success: true, status: 'succeeded' })
|
||
expect(sendDingtalkRobotOtoMessage).toHaveBeenCalledWith(expect.objectContaining({
|
||
userIds: ['ding-user-1'],
|
||
actionTitle: '立即填写',
|
||
}))
|
||
expect(sendDingtalkWorkNotice).not.toHaveBeenCalled()
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({
|
||
status: 'succeeded',
|
||
providerType: 'robot_oto',
|
||
providerMessageId: 'query-1',
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('机器人单聊灰度期间非白名单人员继续使用工作通知', async () => {
|
||
process.env.DINGTALK_PERFORMANCE_SURVEY_NOTIFICATION_CHANNEL = 'robot_oto'
|
||
process.env.DINGTALK_ROBOT_OTO_TEST_USER_IDS = 'ding-user-2'
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(
|
||
notification({ dingtalkUserId: 'ding-user-1' }),
|
||
)
|
||
;(sendDingtalkWorkNotice as jest.Mock).mockResolvedValue({ taskId: 'task-1' })
|
||
|
||
const result = await processPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(result).toEqual({ success: true, status: 'succeeded' })
|
||
expect(sendDingtalkRobotOtoMessage).not.toHaveBeenCalled()
|
||
expect(sendDingtalkWorkNotice).toHaveBeenCalledWith(expect.objectContaining({
|
||
userIds: ['ding-user-1'],
|
||
}))
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({
|
||
providerType: 'work_notice',
|
||
providerMessageId: 'task-1',
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('缺少钉钉 userId 时记录为 skipped,不调用钉钉接口', async () => {
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(notification())
|
||
|
||
const result = await processPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(result).toEqual({ success: false, status: 'skipped' })
|
||
expect(sendDingtalkWorkNotice).not.toHaveBeenCalled()
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({
|
||
status: 'skipped',
|
||
lastError: '收件人缺少钉钉 userId',
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('代他人发起调研时只向其他被考核人发送确认通知', async () => {
|
||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'survey-1',
|
||
title: '七月课程分享',
|
||
creatorUserId: 'user-1',
|
||
creatorName: '发起人',
|
||
creatorEmployeeId: 'employee-self',
|
||
dueAt: new Date('2026-07-01T00:00:00.000Z'),
|
||
targets: [
|
||
{
|
||
id: 'target-self',
|
||
targetType: 'employee',
|
||
employeeProfileId: 'employee-self',
|
||
targetName: '发起人',
|
||
employeeProfile: {
|
||
dingtalkUserId: 'ding-self',
|
||
dingEmail: 'self@example.com',
|
||
},
|
||
},
|
||
{
|
||
id: 'target-other',
|
||
targetType: 'employee',
|
||
employeeProfileId: 'employee-other',
|
||
targetName: '被代发起人',
|
||
employeeProfile: {
|
||
dingtalkUserId: 'ding-other',
|
||
dingEmail: 'other@example.com',
|
||
},
|
||
},
|
||
],
|
||
})
|
||
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
||
dingtalkUserId: 'ding-self',
|
||
email: 'self@example.com',
|
||
})
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(null)
|
||
|
||
const ids = await queueSurveyTargetConfirmationNotifications('survey-1')
|
||
|
||
expect(ids).toEqual(['notification-confirmation'])
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledTimes(1)
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledWith({
|
||
data: expect.objectContaining({
|
||
surveyId: 'survey-1',
|
||
targetId: 'target-other',
|
||
recipientEmployeeId: 'employee-other',
|
||
dingtalkUserId: 'ding-other',
|
||
recipientName: '被代发起人',
|
||
eventType: 'survey.target.confirmation.required',
|
||
businessKey: 'target-confirmation:survey-1:target-other',
|
||
title: '考核调研',
|
||
actionUrl: expect.stringContaining('/dingtalk-entry?redirect='),
|
||
status: 'pending',
|
||
}),
|
||
select: { id: true },
|
||
})
|
||
})
|
||
|
||
test('视觉评分确认通知会直接进入对应作品上传页', async () => {
|
||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'survey-visual',
|
||
title: '七月视觉评分',
|
||
templateCode: 'visual_brand_tone',
|
||
templateSnapshot: { requiredWorkItemCount: 3 },
|
||
creatorUserId: 'user-1',
|
||
creatorName: '发起人',
|
||
creatorEmployeeId: 'employee-self',
|
||
dueAt: new Date('2026-07-01T00:00:00.000Z'),
|
||
targets: [{
|
||
id: 'target-other',
|
||
targetType: 'employee',
|
||
employeeProfileId: 'employee-other',
|
||
targetName: '视觉同事',
|
||
employeeProfile: {
|
||
dingtalkUserId: 'ding-other',
|
||
dingEmail: 'other@example.com',
|
||
},
|
||
}],
|
||
})
|
||
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
||
dingtalkUserId: 'ding-self',
|
||
email: 'self@example.com',
|
||
})
|
||
;(prisma.performanceSurveyNotification.findUnique as jest.Mock).mockResolvedValue(null)
|
||
|
||
await queueSurveyTargetConfirmationNotifications('survey-visual')
|
||
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledWith({
|
||
data: expect.objectContaining({
|
||
title: '考核调研',
|
||
content: expect.stringContaining('**作品要求:** 3 个案例作品'),
|
||
actionUrl: expect.stringContaining(
|
||
'%2Fperformance-survey%2Fsurvey%2Fsurvey-visual%3FtargetId%3Dtarget-other',
|
||
),
|
||
}),
|
||
select: { id: true },
|
||
})
|
||
const createdContent = (prisma.performanceSurveyNotification.create as jest.Mock).mock.calls[0][0].data.content
|
||
expect(createdContent).toContain('### 🖼️ 待提交')
|
||
expect(createdContent).toContain('**七月视觉评分**')
|
||
})
|
||
|
||
test('视觉评分创建后只为自评任务排队通知', async () => {
|
||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'survey-visual',
|
||
title: '七月视觉评分',
|
||
templateCode: 'visual_brand_tone',
|
||
dueAt: null,
|
||
assignments: [
|
||
buildAssignment('self', 'pending', 'assignment-self'),
|
||
buildAssignment('visual_final', 'pending', 'assignment-final'),
|
||
buildAssignment('manager', 'pending', 'assignment-manager'),
|
||
],
|
||
})
|
||
|
||
await queueSurveyAssignmentNotifications('survey-visual')
|
||
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledTimes(1)
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledWith({
|
||
data: expect.objectContaining({
|
||
assignmentId: 'assignment-self',
|
||
title: '考核调研',
|
||
content: [
|
||
'### ◆ 待填写',
|
||
'',
|
||
'**七月视觉评分**',
|
||
'',
|
||
'**评价对象:** 视觉同事 ',
|
||
'',
|
||
'> 请先完成自评;提交后系统才会通知主管和终审人评分',
|
||
].join('\n'),
|
||
}),
|
||
select: { id: true },
|
||
})
|
||
})
|
||
|
||
test('视觉评分自评完成后才为元泽和主管排队通知', async () => {
|
||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'survey-visual',
|
||
title: '七月视觉评分',
|
||
templateCode: 'visual_brand_tone',
|
||
dueAt: null,
|
||
assignments: [
|
||
buildAssignment('self', 'submitted', 'assignment-self'),
|
||
buildAssignment('visual_final', 'pending', 'assignment-final'),
|
||
buildAssignment('manager', 'pending', 'assignment-manager'),
|
||
],
|
||
})
|
||
|
||
await queueSurveyAssignmentNotifications('survey-visual')
|
||
|
||
expect(prisma.performanceSurveyNotification.create).toHaveBeenCalledTimes(2)
|
||
expect((prisma.performanceSurveyNotification.create as jest.Mock).mock.calls.map((call) => call[0].data.assignmentId))
|
||
.toEqual(['assignment-final', 'assignment-manager'])
|
||
})
|
||
|
||
test('维护任务会补建自评完成后缺失的评分通知', async () => {
|
||
;(prisma.performanceSurveyAssignment.findMany as jest.Mock).mockResolvedValue([
|
||
{ surveyId: 'survey-visual' },
|
||
])
|
||
;(prisma.performanceSurvey.findUnique as jest.Mock).mockResolvedValue({
|
||
id: 'survey-visual',
|
||
title: '七月视觉评分',
|
||
templateCode: 'visual_brand_tone',
|
||
dueAt: null,
|
||
assignments: [
|
||
buildAssignment('self', 'submitted', 'assignment-self'),
|
||
buildAssignment('visual_final', 'pending', 'assignment-final'),
|
||
buildAssignment('manager', 'pending', 'assignment-manager'),
|
||
],
|
||
})
|
||
|
||
const recovered = await recoverMissingSurveyAssignmentNotifications()
|
||
|
||
expect(recovered).toBe(2)
|
||
expect(prisma.performanceSurveyAssignment.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
||
where: expect.objectContaining({
|
||
status: 'pending',
|
||
notifications: { none: { eventType: 'survey.assignment.created' } },
|
||
}),
|
||
distinct: ['surveyId'],
|
||
}))
|
||
expect((prisma.performanceSurveyNotification.create as jest.Mock).mock.calls.map((call) => call[0].data.assignmentId))
|
||
.toEqual(['assignment-final', 'assignment-manager'])
|
||
})
|
||
|
||
test('手工补发会重置重试次数和历史发送结果', async () => {
|
||
await retryPerformanceSurveyNotification('notification-1')
|
||
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({
|
||
status: 'pending',
|
||
attemptCount: 0,
|
||
maxAttempts: 5,
|
||
lastStatusCode: null,
|
||
lastResponseBody: null,
|
||
lastError: null,
|
||
providerMessageId: null,
|
||
sentAt: null,
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('维护任务会把长时间 processing 的通知重新入队', async () => {
|
||
;(prisma.performanceSurveyNotification.findMany as jest.Mock).mockResolvedValue([{ id: 'notification-1' }])
|
||
|
||
const recovered = await recoverDuePerformanceSurveyNotifications()
|
||
|
||
expect(recovered).toBe(1)
|
||
expect(prisma.performanceSurveyNotification.updateMany).toHaveBeenCalledWith({
|
||
where: {
|
||
status: 'processing',
|
||
updatedAt: { lte: expect.any(Date) },
|
||
},
|
||
data: expect.objectContaining({
|
||
status: 'retrying',
|
||
lastError: '上次投递处理中断,已重新入队',
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('通知发送结果会把收件人更新为已查看', async () => {
|
||
;(prisma.performanceSurveyNotification.findMany as jest.Mock).mockResolvedValue([{
|
||
id: 'notification-1',
|
||
providerMessageId: 'task-1',
|
||
dingtalkUserId: 'ding-user-1',
|
||
}])
|
||
;(getDingtalkWorkNoticeSendResult as jest.Mock).mockResolvedValue({
|
||
readUserIds: ['ding-user-1'],
|
||
unreadUserIds: [],
|
||
failedUserIds: [],
|
||
invalidUserIds: [],
|
||
forbiddenUserIds: [],
|
||
raw: { errcode: 0 },
|
||
})
|
||
|
||
const count = await refreshPerformanceSurveyNotificationReadStatuses()
|
||
|
||
expect(count).toBe(1)
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({
|
||
readStatus: 'read',
|
||
readAt: expect.any(Date),
|
||
readStatusCheckedAt: expect.any(Date),
|
||
}),
|
||
})
|
||
})
|
||
|
||
test('机器人单聊通知使用 processQueryKey 查询已读状态', async () => {
|
||
;(prisma.performanceSurveyNotification.findMany as jest.Mock).mockResolvedValue([{
|
||
id: 'notification-1',
|
||
providerType: 'robot_oto',
|
||
providerMessageId: 'query-1',
|
||
dingtalkUserId: 'ding-user-1',
|
||
}])
|
||
;(getDingtalkRobotOtoReadResult as jest.Mock).mockResolvedValue({
|
||
readUserIds: ['ding-user-1'],
|
||
unreadUserIds: [],
|
||
sendStatus: 'success',
|
||
deliveryFailed: false,
|
||
raw: { sendStatus: 'success' },
|
||
})
|
||
|
||
const count = await refreshPerformanceSurveyNotificationReadStatuses()
|
||
|
||
expect(count).toBe(1)
|
||
expect(getDingtalkRobotOtoReadResult).toHaveBeenCalledWith('query-1')
|
||
expect(getDingtalkWorkNoticeSendResult).not.toHaveBeenCalled()
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({ readStatus: 'read' }),
|
||
})
|
||
})
|
||
|
||
test('机器人单聊发送状态明确失败时会记录为投递失败', async () => {
|
||
;(prisma.performanceSurveyNotification.findMany as jest.Mock).mockResolvedValue([{
|
||
id: 'notification-1',
|
||
providerType: 'robot_oto',
|
||
providerMessageId: 'query-1',
|
||
dingtalkUserId: 'ding-user-1',
|
||
}])
|
||
;(getDingtalkRobotOtoReadResult as jest.Mock).mockResolvedValue({
|
||
readUserIds: [],
|
||
unreadUserIds: [],
|
||
sendStatus: 'failed',
|
||
deliveryFailed: true,
|
||
raw: { sendStatus: 'failed' },
|
||
})
|
||
|
||
await refreshPerformanceSurveyNotificationReadStatuses()
|
||
|
||
expect(prisma.performanceSurveyNotification.update).toHaveBeenCalledWith({
|
||
where: { id: 'notification-1' },
|
||
data: expect.objectContaining({ readStatus: 'delivery_failed' }),
|
||
})
|
||
})
|
||
})
|