fix: enforce application limits and signature format
This commit is contained in:
@@ -323,6 +323,7 @@ describe('SmsConfigService', () => {
|
||||
interfaceEnabled: false,
|
||||
interfaceType: 'cmpp20',
|
||||
queuePriority: 'priority',
|
||||
dailyLimit: 100000,
|
||||
downstreamReceiptRetryEnabled: true,
|
||||
downstreamUplinkRetryEnabled: true,
|
||||
ipAllowlist: { create: [{ ipCidr: '10.0.0.1/32' }] },
|
||||
@@ -955,20 +956,41 @@ describe('SmsConfigService', () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await service.createSignature({ tenantId: 'tenant-1', name: '运营新建签名' }, { initialAuditStatus: 'approved' });
|
||||
await service.createSignature({ tenantId: 'tenant-1', name: '【运营新建签名】' }, { initialAuditStatus: 'approved' });
|
||||
|
||||
expect(prisma.smsSignature.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ auditStatus: 'approved', name: '运营新建签名' }),
|
||||
data: expect.objectContaining({ auditStatus: 'approved', name: '【运营新建签名】' }),
|
||||
});
|
||||
expect(prisma.auditRecord.create).toHaveBeenCalledWith({ data: expect.objectContaining({ action: 'admin_create_approved', statusAfter: 'approved' }) });
|
||||
});
|
||||
|
||||
it.each(['未带括号', '[英文括号]', '【【重复括号】】', '【 】'])(
|
||||
'rejects a signature name without exactly one complete Chinese black bracket pair: %s',
|
||||
async (name) => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.createSignature({ tenantId: 'tenant-1', name }))
|
||||
.rejects.toThrow('短信签名必须包含完整中文黑括号,例如:【某某科技】');
|
||||
expect(prisma.smsSignature.create).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it('rejects editing a signature to a name without complete Chinese black brackets', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.updateSignature('sig-1', { name: '编辑无括号' }))
|
||||
.rejects.toThrow('短信签名必须包含完整中文黑括号,例如:【某某科技】');
|
||||
expect(prisma.smsSignature.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('updates enterprise signature drainage info through the admin API path', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.updateSignature('sig-1', {
|
||||
name: '签名B',
|
||||
name: '【签名B】',
|
||||
auditStatus: 'approved',
|
||||
drainageInfo: {
|
||||
carrierStatus: { mobile: 'approved', unicom: 'approved', telecom: 'approved' },
|
||||
@@ -976,14 +998,14 @@ describe('SmsConfigService', () => {
|
||||
},
|
||||
})).resolves.toEqual(expect.objectContaining({
|
||||
id: 'sig-1',
|
||||
name: '签名B',
|
||||
name: '【签名B】',
|
||||
auditStatus: 'approved',
|
||||
}));
|
||||
|
||||
expect(prisma.smsSignature.update).toHaveBeenCalledWith({
|
||||
where: { id: 'sig-1' },
|
||||
data: expect.objectContaining({
|
||||
name: '签名B',
|
||||
name: '【签名B】',
|
||||
auditStatus: 'approved',
|
||||
drainageInfo: expect.objectContaining({
|
||||
carrierStatus: expect.objectContaining({ mobile: 'approved' }),
|
||||
|
||||
@@ -385,7 +385,7 @@ export class SmsConfigService {
|
||||
interfaceType,
|
||||
cmppMaxConnections: getPositiveInteger(data.cmppMaxConnections, 1, 'cmppMaxConnections'),
|
||||
cmppWindowSize: getPositiveInteger(data.cmppWindowSize, 16, 'cmppWindowSize'),
|
||||
dailyLimit: data.dailyLimit,
|
||||
dailyLimit: getPositiveInteger(data.dailyLimit, 100000, 'dailyLimit'),
|
||||
customerUnitPrice: data.customerUnitPrice ?? 0,
|
||||
queuePriority,
|
||||
maxPhonesPerTask: data.maxPhonesPerTask ?? 1000000,
|
||||
@@ -452,7 +452,7 @@ export class SmsConfigService {
|
||||
interfaceType,
|
||||
cmppMaxConnections: data.cmppMaxConnections === undefined ? undefined : getPositiveInteger(data.cmppMaxConnections, 1, 'cmppMaxConnections'),
|
||||
cmppWindowSize: data.cmppWindowSize === undefined ? undefined : getPositiveInteger(data.cmppWindowSize, 16, 'cmppWindowSize'),
|
||||
dailyLimit: data.dailyLimit,
|
||||
dailyLimit: data.dailyLimit === undefined ? undefined : getPositiveInteger(data.dailyLimit, 100000, 'dailyLimit'),
|
||||
customerUnitPrice: data.customerUnitPrice,
|
||||
queuePriority,
|
||||
maxPhonesPerTask: data.maxPhonesPerTask,
|
||||
@@ -799,6 +799,7 @@ export class SmsConfigService {
|
||||
}));
|
||||
return {
|
||||
...signature,
|
||||
name: normalizeSmsSignature(signature.name),
|
||||
drainageInfo: { ...legacyPayload, links: drainageLinks },
|
||||
reportTargets: (() => {
|
||||
const channels = routes.filter((route) => route.applicationId === signature.applicationId && route.group).flatMap((route) => route.group!.items.map((item) => item.channel)).filter((channel) => channel.status !== 'deleted');
|
||||
@@ -894,7 +895,7 @@ export class SmsConfigService {
|
||||
id: signature.id,
|
||||
tenantId: signature.tenantId,
|
||||
applicationId: signature.applicationId,
|
||||
name: signature.name,
|
||||
name: normalizeSmsSignature(signature.name),
|
||||
purpose: signature.purpose,
|
||||
auditStatus: signature.auditStatus,
|
||||
reportStatus: signature.reportStatus,
|
||||
@@ -977,11 +978,12 @@ export class SmsConfigService {
|
||||
async createSignature(data: CreateSmsSignatureDto, options: CreateSmsSignatureOptions = {}) {
|
||||
await this.validateSignatureReportValues(data.applicationId, data.drainageInfo);
|
||||
const drainageInfo = await this.withReportRequirementSnapshot(data.applicationId, data.drainageInfo);
|
||||
const name = validateCompleteSmsSignature(data.name);
|
||||
const signature = await this.prisma.smsSignature.create({
|
||||
data: {
|
||||
tenantId: data.tenantId,
|
||||
applicationId: data.applicationId,
|
||||
name: data.name,
|
||||
name,
|
||||
purpose: data.purpose,
|
||||
auditStatus: options.initialAuditStatus,
|
||||
drainageInfo: drainageInfo as Prisma.InputJsonValue | undefined,
|
||||
@@ -1011,8 +1013,9 @@ export class SmsConfigService {
|
||||
const drainageInfo = data.drainageInfo
|
||||
? await this.withReportRequirementSnapshot(applicationId, data.drainageInfo)
|
||||
: undefined;
|
||||
const name = data.name === undefined ? undefined : validateCompleteSmsSignature(data.name);
|
||||
const materialChanged = (data.applicationId !== undefined && data.applicationId !== signature.applicationId)
|
||||
|| (data.name !== undefined && normalizeSmsSignature(data.name) !== normalizeSmsSignature(signature.name))
|
||||
|| (name !== undefined && name !== normalizeSmsSignature(signature.name))
|
||||
|| (data.purpose !== undefined && data.purpose !== signature.purpose)
|
||||
|| (data.drainageInfo !== undefined && JSON.stringify(data.drainageInfo) !== JSON.stringify(signature.drainageInfo ?? null));
|
||||
const auditStatus = materialChanged && signature.auditStatus === 'approved' ? 'pending' : data.auditStatus;
|
||||
@@ -1020,7 +1023,7 @@ export class SmsConfigService {
|
||||
where: { id: signatureId },
|
||||
data: {
|
||||
applicationId: data.applicationId,
|
||||
name: data.name,
|
||||
name,
|
||||
purpose: data.purpose,
|
||||
auditStatus,
|
||||
rejectReason: auditStatus === 'pending' ? null : undefined,
|
||||
@@ -1728,6 +1731,15 @@ function normalizeSmsSignature(name: string) {
|
||||
return innerName ? `【${innerName}】` : '';
|
||||
}
|
||||
|
||||
function validateCompleteSmsSignature(name: string) {
|
||||
const value = name.trim();
|
||||
const match = value.match(/^【([^【】]+)】$/);
|
||||
if (!match || match[1] !== match[1].trim()) {
|
||||
throw new BadRequestException('短信签名必须包含完整中文黑括号,例如:【某某科技】');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function startOfToday() {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
Reference in New Issue
Block a user