const purchaseRequestRoutes = require('../../../src/routes/purchaseRequests'); describe('purchase submit side effects helper', () => { const { finalizePurchaseSubmitSideEffects } = purchaseRequestRoutes.__testables; function createParams(overrides = {}) { return { prisma: {}, config: { imNotifierEnabled: true, emailEnabled: false, }, logger: { error: vi.fn(), warn: vi.fn(), }, requestId: 'req-1', purchaseRequestId: 'purchase-1', requestNo: 'CG-20260324-001', workflowTaskId: 'task-1', assigneeId: 'manager-1', assigneeRole: null, actorId: 'employee-1', loadSummary: vi.fn().mockResolvedValue({ id: 'purchase-1', request_no: 'CG-20260324-001', total_amount: '5600.00', applicant: { id: 'employee-1', name: '李经理', }, }), resolveRecipients: vi.fn().mockResolvedValue(['manager-1']), createAppNotifications: vi.fn().mockResolvedValue([ { id: 'notification-1', recipient_employee_id: 'manager-1', }, ]), dispatchNotifications: vi.fn().mockResolvedValue({ deliveredChannels: [{ channelId: 'channel-1', status: 'success' }], skippedReason: null, }), sendEmailSafe: vi.fn().mockResolvedValue({ email: { skipped: true, sent: 0, failed: 0 }, }), ...overrides, }; } it('does not throw when resolving recipients fails', async () => { const logger = { error: vi.fn(), warn: vi.fn(), }; const createAppNotifications = vi.fn(); const dispatchNotifications = vi.fn(); const sendEmailSafe = vi.fn(); const result = await finalizePurchaseSubmitSideEffects( createParams({ logger, resolveRecipients: vi.fn().mockRejectedValue(new Error('resolve failed')), createAppNotifications, dispatchNotifications, sendEmailSafe, }), ); expect(logger.error).toHaveBeenCalledTimes(1); expect(createAppNotifications).not.toHaveBeenCalled(); expect(dispatchNotifications).not.toHaveBeenCalled(); expect(sendEmailSafe).not.toHaveBeenCalled(); expect(result).toEqual( expect.objectContaining({ recipientEmployeeIds: [], notification: expect.objectContaining({ skipped: true, reason: 'recipient_resolution_failed' }), dispatch: expect.objectContaining({ skippedReason: 'recipient_resolution_failed' }), email: expect.objectContaining({ skipped: true, reason: 'recipient_resolution_failed' }), }), ); }); it('logs notification failures and continues to IM and email', async () => { const logger = { error: vi.fn(), warn: vi.fn(), }; const dispatchNotifications = vi.fn().mockRejectedValue(new Error('dispatch failed')); const sendEmailSafe = vi.fn().mockResolvedValue({ email: { skipped: true, sent: 0, failed: 0 }, }); const result = await finalizePurchaseSubmitSideEffects( createParams({ logger, createAppNotifications: vi.fn().mockRejectedValue(new Error('notifications failed')), dispatchNotifications, sendEmailSafe, }), ); expect(logger.error).toHaveBeenCalledTimes(2); expect(dispatchNotifications).toHaveBeenCalledTimes(1); expect(sendEmailSafe).toHaveBeenCalledTimes(1); expect(result).toEqual( expect.objectContaining({ recipientEmployeeIds: ['manager-1'], notification: expect.objectContaining({ skipped: true, reason: 'notification_failed' }), dispatch: expect.objectContaining({ skippedReason: 'dispatch_failed' }), email: expect.objectContaining({ skipped: true, sent: 0, failed: 0 }), }), ); }); it('builds structured purchase copy for app notification, IM and email', async () => { const createAppNotifications = vi.fn().mockResolvedValue([ { id: 'notification-1', recipient_employee_id: 'manager-1', }, ]); const dispatchNotifications = vi.fn().mockResolvedValue({ deliveredChannels: [{ channelId: 'channel-1', status: 'success' }], skippedReason: null, }); const sendEmailSafe = vi.fn().mockResolvedValue({ email: { skipped: true, sent: 0, failed: 0 }, }); await finalizePurchaseSubmitSideEffects( createParams({ createAppNotifications, dispatchNotifications, sendEmailSafe, }), ); expect(createAppNotifications).toHaveBeenCalledWith( {}, expect.objectContaining({ title: '【待办】采购申请 CG-20260324-001 待审批', body: '申请人:李经理\n金额:5600.00\n单号:CG-20260324-001\n动作:请及时处理审批任务。', }), ); expect(dispatchNotifications).toHaveBeenCalledWith( expect.objectContaining({ prisma: {}, title: '【待办】采购申请 CG-20260324-001 待审批', body: '申请人:李经理\n金额:5600.00\n单号:CG-20260324-001\n动作:请及时处理审批任务。', notifications: [ expect.objectContaining({ id: 'notification-1', }), ], }), ); expect(sendEmailSafe).toHaveBeenCalledWith( expect.objectContaining({ subject: '【待办】采购申请 CG-20260324-001 待审批', body: '申请人:李经理\n金额:5600.00\n单号:CG-20260324-001\n动作:请及时处理审批任务。', }), ); }); });