gongxiangp2/lib/__tests__/performance-survey-notifications.test.ts

163 lines
5.5 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 {
processPerformanceSurveyNotification,
recoverDuePerformanceSurveyNotifications,
retryPerformanceSurveyNotification,
} from '@/lib/performance-survey-notifications'
import { prisma } from '@/lib/prisma'
import { sendDingtalkWorkNotice } from '@/lib/dingtalk-client'
jest.mock('@/lib/prisma', () => ({
prisma: {
performanceSurveyNotification: {
findUnique: jest.fn(),
update: jest.fn(),
updateMany: jest.fn(),
findMany: jest.fn(),
},
performanceSurveyEmployeeProfile: {
findUnique: jest.fn(),
},
user: {
findUnique: jest.fn(),
findFirst: jest.fn(),
},
},
}))
jest.mock('@/lib/dingtalk-client', () => ({
sendDingtalkWorkNotice: 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',
status: 'pending',
attemptCount: 0,
maxAttempts: 5,
...overrides,
}
}
describe('performance survey notifications', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.useFakeTimers()
;(prisma.performanceSurveyNotification.update as jest.Mock).mockResolvedValue({})
;(prisma.performanceSurveyNotification.updateMany as jest.Mock).mockResolvedValue({ count: 0 })
;(prisma.performanceSurveyNotification.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' },
})
})
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',
})
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('缺少钉钉 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 () => {
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: '上次投递处理中断,已重新入队',
}),
})
})
})