fix: harden admin and CMPP delivery workflows

This commit is contained in:
hectorzhao
2026-07-15 11:21:02 +08:00
parent 00b6d95752
commit e47432bc9d
34 changed files with 878 additions and 226 deletions
@@ -94,6 +94,7 @@ function createPrismaMock() {
auditStatus: 'approved',
signature: { auditStatus: 'approved', reportStatus: 'reporting' },
}),
findMany: jest.fn().mockResolvedValue([]),
},
smsSignature: {
findFirst: jest.fn().mockResolvedValue({ id: 'sig-1', name: '签名', auditStatus: 'approved', reportStatus: 'reporting' }),
@@ -571,6 +572,90 @@ describe('SendChainService', () => {
expect(riskReview.aggregateTemplateMismatch).not.toHaveBeenCalled();
});
it('matches an inbound CMPP message against configured template variables and passes extracted values to risk review', async () => {
const { service, prisma, riskReview } = createService();
service.enqueueBatchTask = jest.fn().mockResolvedValue({ taskId: 'task-1', enqueued: 1 });
prisma.smsTemplate.findFirst.mockResolvedValue(null);
prisma.smsTemplate.findMany.mockResolvedValue([{
id: 'tpl-code',
tenantId: 'tenant-1',
applicationId: 'app-1',
content: '【航天信息信诺网】您本次操作的验证码是${code},有效时间10分钟。',
auditStatus: 'approved',
signature: { id: 'sig-1', name: '【航天信息信诺网】', auditStatus: 'approved', reportStatus: 'reporting' },
}]);
await expect(service.submitInboundMessage({
account: '100001',
phoneNumber: '18821203795',
content: '【航天信息信诺网】您本次操作的验证码是715021,有效时间10分钟。',
remoteIp: '127.0.0.1',
})).resolves.toEqual(expect.objectContaining({ accepted: true, messageRecordId: 'record-1' }));
expect(prisma.smsTemplate.findMany).toHaveBeenCalledWith({
where: { applicationId: 'app-1', content: { contains: '${' } },
include: { signature: true },
orderBy: { updatedAt: 'desc' },
});
expect(prisma.smsMessageRecord.create).toHaveBeenCalledWith({
data: expect.objectContaining({ templateId: 'tpl-code', status: 'validating' }),
});
expect(riskReview.evaluateTask).toHaveBeenCalledWith(expect.objectContaining({
templateId: 'tpl-code',
variables: { code: '715021' },
}));
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1');
expect(prisma.smsReceiptRecord.create).not.toHaveBeenCalled();
});
it('queues template-mismatched CMPP content when the application uses direct send', async () => {
const { service, prisma, riskReview } = createService();
service.enqueueBatchTask = jest.fn().mockResolvedValue({ taskId: 'task-1', enqueued: 1 });
prisma.smsApplication.findFirst.mockResolvedValue({
id: 'app-1',
tenantId: 'tenant-1',
cmppAccount: '100001',
status: 'active',
interfaceEnabled: true,
templateMismatchMode: 'direct_send',
customerUnitPrice: 3,
queuePriority: 'normal',
ipAllowlist: [{ ipCidr: '127.0.0.1/32' }],
tenant: { id: 'tenant-1', status: 'active', certificationStatus: 'approved' },
});
prisma.smsTemplate.findFirst.mockResolvedValue(null);
prisma.smsTemplate.findMany.mockResolvedValue([]);
prisma.smsSignature.findFirst.mockResolvedValue({
id: 'sig-1',
name: '【航天信息信诺网】',
auditStatus: 'approved',
reportStatus: 'reporting',
});
await expect(service.submitInboundMessage({
account: '100001',
phoneNumber: '18821203795',
content: '【航天信息信诺网】未配置模板但允许直接发送',
remoteIp: '127.0.0.1',
})).resolves.toEqual(expect.objectContaining({ accepted: true, messageRecordId: 'record-1' }));
expect(prisma.smsSignature.findFirst).toHaveBeenCalledWith({
where: { applicationId: 'app-1', name: '【航天信息信诺网】', auditStatus: 'approved' },
orderBy: { updatedAt: 'desc' },
});
expect(riskReview.evaluateTask).toHaveBeenCalledWith(expect.objectContaining({
applicationId: 'app-1',
content: '【航天信息信诺网】未配置模板但允许直接发送',
}));
expect(riskReview.evaluateTask).toHaveBeenCalledWith(expect.not.objectContaining({ templateId: expect.any(String) }));
expect(prisma.smsMessageRecord.update).toHaveBeenCalledWith({
where: { id: 'record-1' },
data: { status: 'queued', signatureId: 'sig-1' },
});
expect(service.enqueueBatchTask).toHaveBeenCalledWith('task-1');
expect(prisma.smsReceiptRecord.create).not.toHaveBeenCalled();
});
it('aggregates template-mismatched CMPP messages only when the application uses manual review', async () => {
const { service, prisma, riskReview } = createService();
prisma.smsApplication.findFirst.mockResolvedValue({
@@ -1602,6 +1687,29 @@ describe('SendChainService', () => {
it('requeues downstream delivery through real gateway control path', async () => {
const { service, prisma } = createService();
prisma.cmppDownstreamDelivery.findUnique.mockResolvedValueOnce({
id: 'delivery-1',
tenantId: 'tenant-1',
applicationId: 'app-1',
messageId: 'MSG-1',
deliveryType: 'receipt',
status: 'failed',
retryCount: 3,
manualRetryCount: 1,
lastError: 'downstream client is not connected',
payload: { account: '100001', messageId: 'MSG-1', phoneNumber: '13800000001', receiptStatus: 'delivered' },
application: { cmppAccount: '100001' },
});
prisma.cmppDownstreamDelivery.update.mockResolvedValueOnce({
id: 'delivery-1',
tenantId: 'tenant-1',
applicationId: 'app-1',
messageId: 'MSG-1',
deliveryType: 'receipt',
status: 'pending',
retryCount: 0,
manualRetryCount: 2,
});
service['postGatewayControl'] = jest.fn().mockResolvedValue({ delivered: true });
await service.requeueDownstreamDelivery('delivery-1');
@@ -1624,12 +1732,41 @@ describe('SendChainService', () => {
expect(prisma.cmppDownstreamDelivery.update).toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({
status: 'pending',
retryCount: 0,
manualRetryCount: { increment: 1 },
lastRetriedAt: expect.any(Date),
acknowledgedAt: null,
ackResult: null,
ackMessageId: null,
deliveredAt: null,
}),
}));
expect(prisma.operationLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
action: 'gateway.downstream_delivery_requeue',
detail: expect.objectContaining({
previousStatus: 'failed',
previousRetryCount: 3,
manualRetryCount: 2,
lastRetriedAt: expect.any(Date),
}),
}),
});
});
it('rejects manual requeue while downstream acknowledgement is pending', async () => {
const { service, prisma } = createService();
service['postGatewayControl'] = jest.fn();
prisma.cmppDownstreamDelivery.findUnique.mockResolvedValueOnce({
id: 'delivery-1',
status: 'awaiting_ack',
payload: { account: '100001' },
application: { cmppAccount: '100001' },
});
await expect(service.requeueDownstreamDelivery('delivery-1')).rejects.toThrow('该记录正在等待客户端确认,不允许并发重投');
expect(prisma.cmppDownstreamDelivery.update).not.toHaveBeenCalled();
expect(service['postGatewayControl']).not.toHaveBeenCalled();
});
it('supports batch requeue of downstream deliveries', async () => {