feat: harden CMPP delivery and platform workflows

This commit is contained in:
hectorzhao
2026-07-20 18:07:29 +08:00
parent 80fb5a8f53
commit f02c33cbb7
61 changed files with 1834 additions and 281 deletions
+7 -7
View File
@@ -15,22 +15,22 @@ describe('OpenApiService', () => {
const prisma = {
openApiRequest: { findUnique: jest.fn().mockResolvedValue({ bodyHash: 'same', status: 'completed', responseBody: { code: 'ACCEPTED', messageId: 'MSG-1' } }) },
};
const sendChain = { createBatchTask: jest.fn() };
const sendChain = { createHttpBatchTask: jest.fn() };
const service = new OpenApiService(prisma as never, sendChain as never);
const result = await service.sendMessage(auth() as never, { mobile: '18821203795', content: '【测试】短信' }, { idempotencyKey: 'idem-0001', bodyHash: 'same' });
expect(result).toEqual({ code: 'ACCEPTED', messageId: 'MSG-1' });
expect(sendChain.createBatchTask).not.toHaveBeenCalled();
expect(sendChain.createHttpBatchTask).not.toHaveBeenCalled();
});
it('rejects reuse of an idempotency key with a different body', async () => {
const prisma = { openApiRequest: { findUnique: jest.fn().mockResolvedValue({ bodyHash: 'old', status: 'completed' }) } };
const service = new OpenApiService(prisma as never, { createBatchTask: jest.fn() } as never);
const service = new OpenApiService(prisma as never, { createHttpBatchTask: jest.fn() } as never);
await expect(service.sendMessage(auth() as never, { mobile: '18821203795', content: '【测试】短信' }, { idempotencyKey: 'idem-0001', bodyHash: 'new' })).rejects.toBeInstanceOf(ConflictException);
});
it('replays the same persisted business rejection', async () => {
const prisma = { openApiRequest: { findUnique: jest.fn().mockResolvedValue({ bodyHash: 'same', status: 'failed', httpStatus: 422, responseBody: { code: 'SEND_REJECTED', message: '模板不匹配' } }) } };
const service = new OpenApiService(prisma as never, { createBatchTask: jest.fn() } as never);
const service = new OpenApiService(prisma as never, { createHttpBatchTask: jest.fn() } as never);
await expect(service.sendMessage(auth() as never, { mobile: '18821203795', content: '【测试】短信' }, { idempotencyKey: 'idem-0001', bodyHash: 'same' })).rejects.toMatchObject({ status: 422 });
});
@@ -43,10 +43,10 @@ describe('OpenApiService', () => {
},
smsMessageRecord: { findFirst: jest.fn().mockResolvedValue(null) },
};
const sendChain = { createBatchTask: jest.fn().mockResolvedValue({ status: 'ready', messages: [{ id: 'row-1', messageId: 'MSG-1', status: 'queued' }] }) };
const sendChain = { createHttpBatchTask: jest.fn().mockResolvedValue({ status: 'ready', messages: [{ id: 'row-1', messageId: 'MSG-1', status: 'queued' }] }) };
const service = new OpenApiService(prisma as never, sendChain as never);
const result = await service.sendMessage(auth() as never, { mobile: '18821203795', content: '【测试】短信', clientMessageId: 'client-1' }, { idempotencyKey: 'idem-0001', bodyHash: 'hash' });
expect(sendChain.createBatchTask).toHaveBeenCalledWith(expect.objectContaining({ sourceType: 'api', phones: ['18821203795'], clientMessageId: 'client-1' }));
expect(sendChain.createHttpBatchTask).toHaveBeenCalledWith(expect.objectContaining({ phones: ['18821203795'], clientMessageId: 'client-1' }));
expect(result).toEqual(expect.objectContaining({ code: 'ACCEPTED', messageId: 'MSG-1', clientMessageId: 'client-1' }));
expect(prisma.openApiRequest.update).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ status: 'completed', httpStatus: 202, messageRecordId: 'row-1' }) }));
});
@@ -56,7 +56,7 @@ describe('OpenApiService', () => {
openApiRequest: { findUnique: jest.fn().mockResolvedValue(null), create: jest.fn().mockResolvedValue({ id: 'request-row-1' }), update: jest.fn().mockResolvedValue({}) },
smsMessageRecord: { findFirst: jest.fn().mockResolvedValue(null) },
};
const service = new OpenApiService(prisma as never, { createBatchTask: jest.fn().mockRejectedValue(new BadRequestException('短信未匹配模板')) } as never);
const service = new OpenApiService(prisma as never, { createHttpBatchTask: jest.fn().mockRejectedValue(new BadRequestException('短信未匹配模板')) } as never);
await expect(service.sendMessage(auth() as never, { mobile: '18821203795', content: '未匹配模板' }, { idempotencyKey: 'idem-0001', bodyHash: 'hash' })).rejects.toMatchObject({ status: 422 });
expect(prisma.openApiRequest.update).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ status: 'failed', httpStatus: 422, businessCode: 'SEND_REJECTED' }) }));
});