fix: accept scheme-less drainage URLs
This commit is contained in:
@@ -48,6 +48,9 @@ describe('strict client write DTOs', () => {
|
||||
|
||||
it.each([
|
||||
'https://example.com/path',
|
||||
'example.com/path?source=sms',
|
||||
'www.example.com',
|
||||
'192.0.2.10:8080/landing',
|
||||
'13800138000',
|
||||
'+86 138-0013-8000',
|
||||
'0755-12345678',
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { IsBoundedJsonObject } from './bounded-json-object.validator';
|
||||
import { DRAINAGE_TARGET_ERROR, DRAINAGE_TARGET_PATTERN } from './drainage-target';
|
||||
|
||||
export class ClientCertificationSubmissionDto {
|
||||
@IsString() @MinLength(1) @MaxLength(200) companyName!: string;
|
||||
@@ -105,8 +106,8 @@ export class ClientDrainageInfoDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(2048)
|
||||
@Matches(/^(?:https?:\/\/\S+|(?:\+?86[\s-]?)?1(?:[\s-]?\d){10}|(?:\+?86[\s-]?)?(?:\(?0\d{2,3}\)?[\s-]?)?\d{7,8}(?:[\s-]?(?:转|ext\.?)?[\s-]?\d{1,6})?)$/i, {
|
||||
message: '引流信息必须是 http/https URL、手机号码或固定电话号码',
|
||||
@Matches(DRAINAGE_TARGET_PATTERN, {
|
||||
message: DRAINAGE_TARGET_ERROR,
|
||||
})
|
||||
url!: string;
|
||||
@IsOptional() @IsString() @MaxLength(1000) remark?: string;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const DRAINAGE_TARGET_PATTERN = /^(?:(?:https?:\/\/)?(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})|(?:\d{1,3}\.){3}\d{1,3})(?::\d{1,5})?(?:[/?#]\S*)?|(?:\+?86[\s-]?)?1(?:[\s-]?\d){10}|(?:\+?86[\s-]?)?(?:\(?0\d{2,3}\)?[\s-]?)?\d{7,8}(?:[\s-]?(?:转|ext\.?)?[\s-]?\d{1,6})?)$/i;
|
||||
|
||||
export const DRAINAGE_TARGET_ERROR = '引流信息必须是 URL(可不带协议)、手机号码或固定电话号码';
|
||||
|
||||
export function normalizeDrainageTarget(value?: string) {
|
||||
const target = value?.trim() ?? '';
|
||||
return target && DRAINAGE_TARGET_PATTERN.test(target) ? target : undefined;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export class ReportOfficialExportService {
|
||||
sheet.addRow(headers);
|
||||
sheet.addRow(reportType === 'signature'
|
||||
? ['示例签名', '验证码通知', '请在本单元格插入图片', '请在本单元格插入图片', '示例行,导入前请删除']
|
||||
: ['示例签名', 'https://example.com 或 13800138000', '示例行,导入前请删除', '请在本单元格插入图片']);
|
||||
: ['示例签名', 'example.com/path 或 13800138000', '示例行,导入前请删除', '请在本单元格插入图片']);
|
||||
styleHeader(sheet.getRow(1));
|
||||
sheet.columns.forEach((column) => { column.width = 24; });
|
||||
sheet.getRow(2).height = 48;
|
||||
|
||||
@@ -10,14 +10,11 @@ import { APPLICATION_DISABLE_GRACE_MS, DEFAULT_APPLICATION_DISABLE_SCAN_INTERVAL
|
||||
import { SmsReportValidationService } from './report-validation.service';
|
||||
import { SmsAuditService } from './audit.service';
|
||||
import { shanghaiDateRange } from '../common/shanghai-date-range';
|
||||
|
||||
const DRAINAGE_TARGET_PATTERN = /^(?:https?:\/\/\S+|(?:\+?86[\s-]?)?1(?:[\s-]?\d){10}|(?:\+?86[\s-]?)?(?:\(?0\d{2,3}\)?[\s-]?)?\d{7,8}(?:[\s-]?(?:转|ext\.?)?[\s-]?\d{1,6})?)$/i;
|
||||
import { DRAINAGE_TARGET_ERROR, normalizeDrainageTarget as readDrainageTarget } from '../common/drainage-target';
|
||||
|
||||
function normalizeDrainageTarget(value?: string) {
|
||||
const target = value?.trim() ?? '';
|
||||
if (!target || !DRAINAGE_TARGET_PATTERN.test(target)) {
|
||||
throw new BadRequestException('引流信息必须是 http/https URL、手机号码或固定电话号码');
|
||||
}
|
||||
const target = readDrainageTarget(value);
|
||||
if (!target) throw new BadRequestException(DRAINAGE_TARGET_ERROR);
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
@@ -1024,6 +1024,20 @@ describe('SmsConfigService', () => {
|
||||
expect(prisma.channelSignatureReportTask.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('accepts a scheme-less drainage URL and synchronizes the compatibility name', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsSignature.findUnique.mockResolvedValue({ id: 'sig-1', tenantId: 'tenant-1', applicationId: 'app-1', auditStatus: 'approved' });
|
||||
prisma.channelRouteRule.findMany.mockResolvedValue([]);
|
||||
const service = new SmsConfigService(prisma as never);
|
||||
|
||||
await expect(service.createDrainageInfo('sig-1', { url: 'example.com/path', reportValues: {} }, {}, 'tenant-1'))
|
||||
.resolves.toEqual(expect.objectContaining({ id: 'drainage-1', auditStatus: 'pending' }));
|
||||
|
||||
expect(prisma.smsDrainageInfo.create).toHaveBeenCalledWith(expect.objectContaining({
|
||||
data: expect.objectContaining({ siteName: 'example.com/path', url: 'example.com/path' }),
|
||||
}));
|
||||
});
|
||||
|
||||
it('resets an approved signature to pending when key content is changed', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsSignature.findUnique.mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user