feat: harden platform workflows and UI governance

This commit is contained in:
hectorzhao
2026-07-22 14:14:55 +08:00
parent ef957f7daa
commit 0f223f7f91
80 changed files with 4958 additions and 764 deletions
+66 -1
View File
@@ -543,7 +543,16 @@ describe('SmsConfigService', () => {
}),
});
expect(prisma.cmppDownstreamConnection.deleteMany).toHaveBeenCalledWith({
where: { status: 'connected', lastHeartbeatAt: { lt: new Date('2026-07-11T10:58:30.000Z') } },
where: {
status: 'connected',
OR: [
{ lastHeartbeatAt: { lt: new Date('2026-07-11T10:58:30.000Z') } },
{
lastHeartbeatAt: null,
connectedAt: { lt: new Date('2026-07-11T10:58:30.000Z') },
},
],
},
});
expect(prisma.operationLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
@@ -554,6 +563,27 @@ describe('SmsConfigService', () => {
});
});
it('prunes expired downstream connections whose heartbeat was never recorded', async () => {
const prisma = createPrismaMock();
const service = new SmsConfigService(prisma as never);
const now = new Date('2026-07-21T04:00:00.000Z');
await service.markTimedOutDownstreamConnections(now);
expect(prisma.cmppDownstreamConnection.deleteMany).toHaveBeenCalledWith({
where: {
status: 'connected',
OR: [
{ lastHeartbeatAt: { lt: new Date('2026-07-21T03:58:30.000Z') } },
{
lastHeartbeatAt: null,
connectedAt: { lt: new Date('2026-07-21T03:58:30.000Z') },
},
],
},
});
});
it('rejects connection heartbeats after an IP allowlist change or connection-limit reduction', async () => {
const prisma = createPrismaMock();
prisma.smsApplication.findUnique.mockResolvedValue({
@@ -866,6 +896,41 @@ describe('SmsConfigService', () => {
expect(prisma.channelSignatureReportTask.create).not.toHaveBeenCalled();
});
it('resets an approved signature to pending when key content is changed', async () => {
const prisma = createPrismaMock();
prisma.smsSignature.findUnique.mockResolvedValue({
id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1', name: '【旧签名】',
purpose: '通知', drainageInfo: {}, auditStatus: 'approved',
});
const service = new SmsConfigService(prisma as never);
await service.updateSignature('sig-1', { name: '【新签名】' });
expect(prisma.smsSignature.update).toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({ auditStatus: 'pending', rejectReason: null }),
}));
});
it('resets an approved template to pending when key content is changed', async () => {
const prisma = createPrismaMock();
prisma.smsTemplate.findUnique.mockResolvedValue({
id: 'tpl-1', tenantId: 'tenant-1', applicationId: 'app-1', signatureId: 'sig-1',
name: '模板A', content: '【签名A】验证码${code}', category: '验证码', auditStatus: 'approved',
});
const tx = {
templateVariable: { deleteMany: jest.fn().mockResolvedValue({ count: 1 }) },
smsTemplate: { update: jest.fn().mockResolvedValue({ id: 'tpl-1', auditStatus: 'pending' }) },
};
prisma.$transaction.mockImplementationOnce((callback: (client: typeof tx) => unknown) => callback(tx));
const service = new SmsConfigService(prisma as never);
await service.updateTemplate('tpl-1', { content: '【签名A】您的验证码为${code}' });
expect(tx.smsTemplate.update).toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({ auditStatus: 'pending' }),
}));
});
it('creates real drainage materials and channel tasks after operations approval', async () => {
const prisma = createPrismaMock();
const pendingItem = { id: 'drainage-1', tenantId: 'tenant-1', signatureId: 'sig-1', applicationId: 'app-1', siteName: '官网', url: 'https://example.com', reportValues: { site_owner: '企业A' }, auditStatus: 'pending' };