Files
lislgosms/api/src/sms-config/sms-config.service.spec.ts
T

122 lines
4.2 KiB
TypeScript

import { SmsConfigService } from './sms-config.service';
function createPrismaMock() {
return {
smsApplication: {
findMany: jest.fn().mockResolvedValue([{
id: 'app-1',
tenantId: 'tenant-1',
name: '应用A',
status: 'active',
tenant: { id: 'tenant-1', name: '租户A', code: 'TENANT-A' },
messageRecords: [{ status: 'delivered' }, { status: 'undelivered' }],
}]),
findUnique: jest.fn().mockResolvedValue({
id: 'app-1',
tenantId: 'tenant-1',
name: '应用A',
status: 'active',
secretHash: 'secret-hash',
tenant: { id: 'tenant-1', name: '租户A', code: 'TENANT-A' },
}),
},
smsSignature: {
findUnique: jest.fn().mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', auditStatus: 'pending' }),
update: jest.fn(),
},
smsTemplate: {
findUnique: jest.fn().mockResolvedValue({ id: 'tpl-1', tenantId: 'tenant-1', auditStatus: 'pending' }),
update: jest.fn(),
},
auditRecord: {
create: jest.fn(),
findMany: jest.fn(),
},
user: {
findUnique: jest.fn().mockResolvedValue(null),
},
cmppConnectionState: {
findMany: jest.fn().mockResolvedValue([{ channelId: 'channel-1', connectionId: 'conn-a', tenantId: 'tenant-1', status: 'online', currentConnections: 1, desiredConnections: 1 }]),
findFirst: jest.fn().mockResolvedValue({ channelId: 'channel-1', connectionId: 'conn-a', tenantId: 'tenant-1' }),
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'conn-state-1', ...data })),
},
smsChannel: {
findFirst: jest.fn().mockResolvedValue({
id: 'channel-1',
gatewayHost: '127.0.0.1',
gatewayPort: 17890,
enterpriseCode: 'EC',
account: 'sp',
passwordCipher: 'cipher',
srcId: '10690000',
cmppVersion: '3.0',
config: { maxConnections: 2 },
}),
},
operationLog: {
create: jest.fn(),
},
};
}
describe('SmsConfigService', () => {
it('rejects unknown reviewer ids before writing audit records', async () => {
const prisma = createPrismaMock();
const service = new SmsConfigService(prisma as never);
await expect(service.approveSignature('sig-1', { reviewerId: 'missing-user' })).rejects.toThrow(
'reviewerId does not reference an existing user',
);
expect(prisma.smsSignature.update).not.toHaveBeenCalled();
expect(prisma.auditRecord.create).not.toHaveBeenCalled();
});
it('lists enterprise applications with real CMPP connection state', async () => {
const prisma = createPrismaMock();
const service = new SmsConfigService(prisma as never);
await expect(service.listApplications({ includeConnections: true })).resolves.toEqual([
expect.objectContaining({
id: 'app-1',
cmppStatus: 'connected',
sentToday: 2,
deliveryRate: 50,
cmppConnections: [expect.objectContaining({ connectionId: 'conn-a' })],
}),
]);
});
it('returns CMPP params from persisted application and channel config', async () => {
const prisma = createPrismaMock();
const service = new SmsConfigService(prisma as never);
await expect(service.getApplicationCmppParams('app-1')).resolves.toEqual(expect.objectContaining({
applicationId: 'app-1',
tenantName: '租户A',
gatewayHost: '127.0.0.1',
gatewayPort: 17890,
maxConnections: 2,
}));
});
it('disconnects application CMPP connections and writes operation logs', async () => {
const prisma = createPrismaMock();
const service = new SmsConfigService(prisma as never);
await service.disconnectApplicationConnection('app-1', 'conn-a', { status: 'disconnected', reason: 'manual' });
expect(prisma.cmppConnectionState.update).toHaveBeenCalledWith({
where: { channelId_connectionId: { channelId: 'channel-1', connectionId: 'conn-a' } },
data: expect.objectContaining({ status: 'disconnected', currentConnections: 0, lastError: 'manual' }),
});
expect(prisma.operationLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
action: 'cmpp_connection.disconnected',
resource: 'cmpp_connection',
resourceId: 'channel-1:conn-a',
}),
});
});
});