feat: harden platform workflows and UI governance
This commit is contained in:
@@ -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' };
|
||||
|
||||
Reference in New Issue
Block a user