feat: add HTTP API and complete client workflows
This commit is contained in:
@@ -65,6 +65,7 @@ function createPrismaMock() {
|
||||
count: jest.fn().mockResolvedValue(0),
|
||||
},
|
||||
smsSignature: {
|
||||
groupBy: jest.fn().mockResolvedValue([]),
|
||||
findMany: jest.fn().mockResolvedValue([{
|
||||
id: 'sig-1',
|
||||
tenantId: 'tenant-1',
|
||||
@@ -224,7 +225,7 @@ describe('SmsConfigService', () => {
|
||||
]);
|
||||
expect(prisma.smsApplication.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: expect.objectContaining({ status: 'active', tenant: { name: { contains: '租户' } }, name: { contains: '应用' } }),
|
||||
include: { tenant: true, ipAllowlist: true },
|
||||
include: { tenant: true, ipAllowlist: true, httpConfig: true },
|
||||
}));
|
||||
expect(prisma.smsApplication.findMany.mock.calls[0][0]).not.toHaveProperty('take');
|
||||
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -661,6 +662,86 @@ describe('SmsConfigService', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes channel sources from client report-field responses', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.channelRouteRule.findMany.mockResolvedValue([{
|
||||
id: 'route-1', priority: 10,
|
||||
group: {
|
||||
id: 'group-1', name: '内部通道组',
|
||||
items: [{ channel: { id: 'channel-secret', code: 'SECRET-CH', name: '内部通道', reportFields: [{ status: 'active', required: true, reportType: 'signature', drainageField: { id: 'field-1', code: 'license', name: '营业执照', fieldType: 'file', description: null, status: 'active' } }] } }],
|
||||
},
|
||||
}] as never);
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
const fields = await service.getClientApplicationReportFields('app-1', 'signature');
|
||||
|
||||
expect(fields).toEqual([expect.objectContaining({ id: 'field-1', code: 'license', required: true })]);
|
||||
expect(JSON.stringify(fields)).not.toContain('channel-secret');
|
||||
expect(JSON.stringify(fields)).not.toContain('内部通道');
|
||||
expect(fields[0]).not.toHaveProperty('channels');
|
||||
});
|
||||
|
||||
it('returns client signatures without report tasks, channels, or internal requirement snapshots', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsSignature.findMany.mockResolvedValue([{
|
||||
id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1', name: '签名A', purpose: '通知',
|
||||
auditStatus: 'rejected', rejectReason: '请补充资料',
|
||||
drainageInfo: { signatureReportValues: { license: 'file-1' }, reportRequirements: [{ channelId: 'channel-secret', channelName: '内部通道' }] },
|
||||
createdAt: new Date('2026-07-16T01:00:00Z'), updatedAt: new Date('2026-07-16T02:00:00Z'),
|
||||
application: { id: 'app-1', name: '应用A', status: 'active' },
|
||||
materials: [{ id: 'material-1', fileObjectId: 'file-1', materialType: 'license', title: '营业执照', description: null, createdAt: new Date() }],
|
||||
drainageItems: [{ id: 'drainage-1', siteName: '官网', url: 'https://example.com', remark: null, reportValues: { owner: '企业A' }, auditStatus: 'pending', rejectReason: null, submittedAt: new Date(), reviewedAt: null, createdAt: new Date(), updatedAt: new Date() }],
|
||||
_count: { reportMaterials: 2 },
|
||||
reportTasks: [{ channelId: 'channel-secret' }],
|
||||
}] as never);
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
const result = await service.listClientSignatures('tenant-1');
|
||||
const serialized = JSON.stringify(result);
|
||||
|
||||
expect(result[0]).toEqual(expect.objectContaining({
|
||||
id: 'sig-1',
|
||||
submittedMaterialCount: 3,
|
||||
reportValues: { license: 'file-1' },
|
||||
drainageInfo: { links: [expect.objectContaining({ id: 'drainage-1', siteName: '官网' })] },
|
||||
}));
|
||||
expect(serialized).not.toContain('channel-secret');
|
||||
expect(serialized).not.toContain('内部通道');
|
||||
expect(result[0]).not.toHaveProperty('reportTasks');
|
||||
expect(result[0]).not.toHaveProperty('reportStatus');
|
||||
});
|
||||
|
||||
it('returns real client signature workspace counts from database grouping', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsSignature.findMany.mockResolvedValue([]);
|
||||
prisma.smsSignature.groupBy.mockResolvedValue([
|
||||
{ auditStatus: 'pending', _count: { _all: 2 } },
|
||||
{ auditStatus: 'approved', _count: { _all: 5 } },
|
||||
{ auditStatus: 'rejected', _count: { _all: 1 } },
|
||||
] as never);
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.getClientSignatureWorkspace('tenant-1')).resolves.toEqual({
|
||||
items: [],
|
||||
summary: { total: 8, pending: 2, approved: 5, rejected: 1, draft: 0 },
|
||||
});
|
||||
expect(prisma.smsSignature.groupBy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: { tenantId: 'tenant-1', auditStatus: { notIn: ['deleted', 'disabled'] } },
|
||||
}));
|
||||
});
|
||||
|
||||
it('selects client drainage information without internal tasks or channels', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsDrainageInfo.findMany.mockResolvedValue([{ id: 'drainage-1', siteName: '官网' }] as never);
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.listClientDrainageInfos('tenant-1')).resolves.toEqual([{ id: 'drainage-1', siteName: '官网' }]);
|
||||
const query = prisma.smsDrainageInfo.findMany.mock.calls[0][0];
|
||||
expect(query.where).toEqual({ id: undefined, tenantId: 'tenant-1', auditStatus: { not: 'deleted' } });
|
||||
expect(query.select).not.toHaveProperty('reportTasks');
|
||||
expect(JSON.stringify(query.select)).not.toContain('channel');
|
||||
});
|
||||
|
||||
it('requires common signature fields even when a signature is not bound to an application', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.commonReportField.findMany.mockResolvedValue([{
|
||||
|
||||
Reference in New Issue
Block a user