feat: improve application access and money precision
This commit is contained in:
@@ -38,6 +38,7 @@ function createPrismaMock() {
|
||||
interfaceType: 'cmpp20',
|
||||
queuePriority: 'normal',
|
||||
secretHash: '0123456789abcdef',
|
||||
ipAllowlist: [],
|
||||
tenant: { id: 'tenant-1', name: '租户A', code: 'TENANT-A' },
|
||||
}),
|
||||
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'app-1', tenantId: 'tenant-1', ...data })),
|
||||
@@ -261,9 +262,11 @@ describe('SmsConfigService', () => {
|
||||
expect(applications.map((application) => application.id)).toEqual(['app-3', 'app-2', 'app-1']);
|
||||
});
|
||||
|
||||
it('returns CMPP params from persisted application and channel config', async () => {
|
||||
it('returns CMPP params from the public inbound Gateway config instead of an upstream channel', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
process.env.CMPP_PUBLIC_HOST = 'cmpp.example.com';
|
||||
process.env.CMPP_PUBLIC_PORT = '17891';
|
||||
|
||||
await expect(service.getApplicationCmppParams('app-1')).resolves.toEqual(expect.objectContaining({
|
||||
applicationId: 'app-1',
|
||||
@@ -275,14 +278,17 @@ describe('SmsConfigService', () => {
|
||||
applicationExtension: '0001',
|
||||
accessNumberFillEnabled: true,
|
||||
accessNumberFillPrefix: '00',
|
||||
gatewayHost: '127.0.0.1',
|
||||
gatewayPort: 17890,
|
||||
gatewayHost: 'cmpp.example.com',
|
||||
gatewayPort: 17891,
|
||||
interfaceEnabled: true,
|
||||
interfaceType: 'cmpp20',
|
||||
maxConnections: 2,
|
||||
windowSize: 32,
|
||||
protocolVersion: 'CMPP2.0',
|
||||
}));
|
||||
expect(prisma.smsChannel.findFirst).not.toHaveBeenCalled();
|
||||
delete process.env.CMPP_PUBLIC_HOST;
|
||||
delete process.env.CMPP_PUBLIC_PORT;
|
||||
});
|
||||
|
||||
it('creates enterprise applications with persisted queue priority', async () => {
|
||||
@@ -428,7 +434,9 @@ describe('SmsConfigService', () => {
|
||||
prisma.$transaction.mockImplementationOnce((callback: (client: typeof tx) => unknown) => callback(tx));
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.updateApplication('app-1', { name: '新应用', customerUnitPrice: 300, queuePriority: 'priority', ipAllowlist: ['10.0.0.1/32'] }))
|
||||
await expect(service.updateApplication('app-1', { customerUnitPrice: 325.5 }))
|
||||
.rejects.toThrow('客户单价最多支持人民币小数点后 4 位');
|
||||
await expect(service.updateApplication('app-1', { name: '新应用', customerUnitPrice: 325, queuePriority: 'priority', ipAllowlist: ['10.0.0.1/32'] }))
|
||||
.resolves.toEqual(expect.objectContaining({ id: 'app-1', name: '新应用' }));
|
||||
|
||||
expect(tx.smsApplicationIpAllowlist.deleteMany).toHaveBeenCalledWith({ where: { applicationId: 'app-1' } });
|
||||
@@ -437,7 +445,7 @@ describe('SmsConfigService', () => {
|
||||
data: expect.objectContaining({
|
||||
name: '新应用',
|
||||
cmppEnterpriseCode: '100001',
|
||||
customerUnitPrice: 300,
|
||||
customerUnitPrice: 325,
|
||||
queuePriority: 'priority',
|
||||
ipAllowlist: { create: [{ ipCidr: '10.0.0.1/32' }] },
|
||||
}),
|
||||
@@ -498,6 +506,17 @@ describe('SmsConfigService', () => {
|
||||
await expect(service.getApplicationCmppParams('app-1', 'tenant-2')).rejects.toThrow('Application not found');
|
||||
});
|
||||
|
||||
it('forbids client CMPP parameter access when the interface is not enabled', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsApplication.findUnique.mockResolvedValue({
|
||||
id: 'app-1', tenantId: 'tenant-1', interfaceEnabled: false,
|
||||
tenant: { id: 'tenant-1', name: '租户A' },
|
||||
});
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.getApplicationCmppParams('app-1', 'tenant-1')).rejects.toThrow('该企业应用未开通 CMPP 接口');
|
||||
});
|
||||
|
||||
it('records Gateway downstream CMPP connection and heartbeat events against the real application account', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
@@ -531,6 +550,31 @@ describe('SmsConfigService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects connection heartbeats after an IP allowlist change or connection-limit reduction', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsApplication.findUnique.mockResolvedValue({
|
||||
id: 'app-1', tenantId: 'tenant-1', cmppEnterpriseCode: 'APP-EC', cmppMaxConnections: 1,
|
||||
interfaceEnabled: true, status: 'active', ipAllowlist: [{ ipCidr: '10.0.0.0/24' }],
|
||||
});
|
||||
prisma.cmppDownstreamConnection.findUnique.mockResolvedValue({ id: 'downstream-2', connectedAt: new Date() });
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.recordDownstreamConnectionEvent({
|
||||
account: '100001', connectionId: 'gateway-1-2', status: 'heartbeat', remoteIp: '127.0.0.1',
|
||||
})).rejects.toThrow('CMPP source IP is not in application allowlist');
|
||||
|
||||
prisma.smsApplication.findUnique.mockResolvedValue({
|
||||
id: 'app-1', tenantId: 'tenant-1', cmppEnterpriseCode: 'APP-EC', cmppMaxConnections: 1,
|
||||
interfaceEnabled: true, status: 'active', ipAllowlist: [],
|
||||
});
|
||||
prisma.cmppDownstreamConnection.findMany.mockResolvedValue([
|
||||
{ connectionId: 'gateway-1-1' }, { connectionId: 'gateway-1-2' },
|
||||
]);
|
||||
await expect(service.recordDownstreamConnectionEvent({
|
||||
account: '100001', connectionId: 'gateway-1-2', status: 'heartbeat', remoteIp: '127.0.0.1',
|
||||
})).rejects.toThrow('CMPP connection limit exceeded (1)');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['heartbeat', 'lastHeartbeatAt'],
|
||||
['submit', 'lastSubmitAt'],
|
||||
|
||||
Reference in New Issue
Block a user