文件
OmniCard/backend/test/modules/team/domain.test.ts
wikiboss eec259a3a7 feat: 完成全系统30个功能点开发与E2E全链路验收 (8轮调度32个子代理闭环)
- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换
- 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片
- 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转
- 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出
- 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志
- 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票
- 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏
- 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
2026-09-20 23:47:47 +08:00

422 行
15 KiB
TypeScript

import { getMetadataArgsStorage } from 'typeorm';
import { CoolCommException } from '@cool-midway/core';
import { BaseEntity } from '../../../src/comm/entity/base';
import { TeamDomainEntity } from '../../../src/modules/team/entity/domain';
import { TeamDomainService } from '../../../src/modules/team/service/domain';
import { AdminTeamDomainController } from '../../../src/modules/team/controller/admin/domain';
import { SslCertMockInfo } from '../../../src/comm/types/omni';
describe('Point 22: 独立企业二级域名绑定向导与 SSL 证书检测 单元测试', () => {
const metadata = getMetadataArgsStorage();
// ==========================================
// 1. 实体定义与数据库字段映射测试
// ==========================================
describe('1. 实体定义与数据库字段映射测试', () => {
it('TeamDomainEntity 表名映射应为 team_domain 且继承 BaseEntity', () => {
const table = metadata.tables.find(t => t.target === TeamDomainEntity);
expect(table).toBeDefined();
expect(table?.name).toBe('team_domain');
expect(TeamDomainEntity.prototype instanceof BaseEntity).toBe(true);
});
it('TeamDomainEntity 应映射核心列名与类型约束', () => {
const columns = metadata.columns.filter(c => c.target === TeamDomainEntity);
const customDomainCol = columns.find(c => c.propertyName === 'customDomain');
const sslStatusCol = columns.find(c => c.propertyName === 'sslStatus');
const cnameTargetCol = columns.find(c => c.propertyName === 'cnameTarget');
const sslExpireTimeCol = columns.find(c => c.propertyName === 'sslExpireTime');
expect(customDomainCol).toBeDefined();
expect(customDomainCol?.options.name || customDomainCol?.propertyName).toBe('custom_domain');
expect(sslStatusCol).toBeDefined();
expect(sslStatusCol?.options.name || sslStatusCol?.propertyName).toBe('ssl_status');
expect(cnameTargetCol).toBeDefined();
expect(cnameTargetCol?.options.name || cnameTargetCol?.propertyName).toBe('cname_target');
expect(sslExpireTimeCol).toBeDefined();
expect(sslExpireTimeCol?.options.name || sslExpireTimeCol?.propertyName).toBe('ssl_expire_time');
});
});
// ==========================================
// 2. 域名格式正则严格校验 (validateDomainFormat)
// ==========================================
describe('2. 域名格式正则严格校验 (validateDomainFormat)', () => {
let service: TeamDomainService;
beforeEach(() => {
service = new TeamDomainService();
});
it('合法企业独立二级与多级域名应通过校验', () => {
const validDomains = [
'card.mycompany.com',
'portal.omnicard.ai',
'sales.global-biz.co.uk',
'vip.vip-shop.cn',
'a.b.c.example.org',
];
for (const domain of validDomains) {
const result = service.validateDomainFormat(domain);
expect(result.valid).toBe(true);
expect(result.reason).toBeUndefined();
}
});
it('空值或纯空格域名应判定为非法', () => {
expect(service.validateDomainFormat('').valid).toBe(false);
expect(service.validateDomainFormat(' ').valid).toBe(false);
});
it('拦截包含端口号的域名', () => {
const result = service.validateDomainFormat('card.example.com:8080');
expect(result.valid).toBe(false);
expect(result.reason).toContain('端口');
});
it('拦截带有 HTTP/HTTPS 协议前缀的输入', () => {
expect(service.validateDomainFormat('http://card.example.com').valid).toBe(false);
expect(service.validateDomainFormat('https://card.example.com').valid).toBe(false);
expect(service.validateDomainFormat('card.example.com/').valid).toBe(false);
});
it('拦截 IPv4 与 IPv6 地址输入', () => {
const ipList = ['192.168.1.1', '10.0.0.1', '127.0.0.1', '::1', '2001:db8::1'];
for (const ip of ipList) {
const result = service.validateDomainFormat(ip);
expect(result.valid).toBe(false);
expect(result.reason).toMatch(/IP/i);
}
});
it('拦截内部保留域名及特定后缀', () => {
const reservedList = [
'localhost',
'card.localhost',
'service.local',
'api.internal',
'dev.lan',
'test.test',
'demo.example',
'mock.invalid',
];
for (const domain of reservedList) {
const result = service.validateDomainFormat(domain);
expect(result.valid).toBe(false);
expect(result.reason).toContain('保留');
}
});
it('拦截含有下划线、空格或特殊字符的非法域名', () => {
const invalidChars = [
'card_domain.com',
'my brand.com',
'@portal.com',
'card#name.com',
'-bad.com',
'bad-.com',
'.bad.com',
'bad.com.',
];
for (const domain of invalidChars) {
const result = service.validateDomainFormat(domain);
expect(result.valid).toBe(false);
}
});
it('拦截未达到二级域名的单层主机名', () => {
const result = service.validateDomainFormat('singlehostname');
expect(result.valid).toBe(false);
});
});
// ==========================================
// 3. DNS CNAME 记录核验 (verifyDnsRecord)
// ==========================================
describe('3. DNS CNAME 记录核验 (verifyDnsRecord)', () => {
let service: TeamDomainService;
beforeEach(() => {
service = new TeamDomainService();
});
it('当 CNAME 记录匹配预期 targetCname 时返回 verified=true (支持大小写与尾部点自动归一化)', async () => {
const res = await service.verifyDnsRecord('card.mycompany.com', 'cname.omnicard.ai', {
mockRecords: ['cname.omnicard.ai.'],
});
expect(res.verified).toBe(true);
expect(res.targetCname).toBe('cname.omnicard.ai');
expect(res.records).toEqual(['cname.omnicard.ai.']);
});
it('当解析结果为大写或混合且包含目标时匹配成功', async () => {
const res = await service.verifyDnsRecord('card.mycompany.com', 'CNAME.OMNICARD.AI', {
mockRecords: ['cname.omnicard.ai'],
});
expect(res.verified).toBe(true);
});
it('当 CNAME 记录不匹配时返回 verified=false', async () => {
const res = await service.verifyDnsRecord('card.mycompany.com', 'cname.omnicard.ai', {
mockRecords: ['wrong-cname.otherdomain.com'],
});
expect(res.verified).toBe(false);
expect(res.message).toContain('不匹配');
});
it('模拟或真实解析发生空记录或错误时友好降级为 verified=false', async () => {
const res = await service.verifyDnsRecord('card.mycompany.com', 'cname.omnicard.ai', {
mockRecords: [],
});
expect(res.verified).toBe(false);
expect(res.records).toEqual([]);
});
});
// ==========================================
// 4. SSL 证书检测与过期时间计算 (checkSslCertificate)
// ==========================================
describe('4. SSL 证书状态与过期时间计算 (checkSslCertificate)', () => {
let service: TeamDomainService;
beforeEach(() => {
service = new TeamDomainService();
});
it('有效证书应计算准确的剩余天数并标记 status=active', async () => {
const futureDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
const pastDate = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString();
const mockCert: SslCertMockInfo = {
issuer: "Let's Encrypt Authority X3",
validFrom: pastDate,
validTo: futureDate,
authorized: true,
};
const res = await service.checkSslCertificate('card.mycompany.com', 443, { mockCert });
expect(res.valid).toBe(true);
expect(res.status).toBe('active');
expect(res.expired).toBe(false);
expect(res.daysRemaining).toBeGreaterThanOrEqual(29);
expect(res.issuer).toBe("Let's Encrypt Authority X3");
});
it('过期证书应标记 status=failed 且 expired=true', async () => {
const pastDate = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
const olderDate = new Date(Date.now() - 95 * 24 * 60 * 60 * 1000).toISOString();
const mockCert: SslCertMockInfo = {
issuer: 'DigiCert Global Root CA',
validFrom: olderDate,
validTo: pastDate,
authorized: true,
};
const res = await service.checkSslCertificate('card.mycompany.com', 443, { mockCert });
expect(res.valid).toBe(false);
expect(res.status).toBe('failed');
expect(res.expired).toBe(true);
expect(res.daysRemaining).toBeLessThanOrEqual(0);
expect(res.errorMessage).toContain('过期');
});
it('未经授权/未受信任证书应标记 status=failed', async () => {
const futureDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
const mockCert: SslCertMockInfo = {
issuer: 'Self-Signed Untrusted Root',
validFrom: new Date().toISOString(),
validTo: futureDate,
authorized: false,
};
const res = await service.checkSslCertificate('card.mycompany.com', 443, { mockCert });
expect(res.valid).toBe(false);
expect(res.status).toBe('failed');
expect(res.errorMessage).toBeDefined();
});
});
// ==========================================
// 5. 绑定域名持久化业务逻辑 (bindDomain)
// ==========================================
describe('5. 绑定域名持久化业务逻辑 (bindDomain)', () => {
let service: TeamDomainService;
let domains: TeamDomainEntity[];
let nextId = 1;
beforeEach(() => {
domains = [];
nextId = 1;
// 预置已被租户 88 绑定的域名
const existing = new TeamDomainEntity();
existing.id = nextId++;
existing.tenantId = 88;
existing.customDomain = 'taken.brand.com';
existing.sslStatus = 'active';
domains.push(existing);
const mockRepo = {
findOne: jest.fn(async (options: { where: Partial<TeamDomainEntity> }) => {
return (
domains.find(d => {
if (options.where.customDomain && d.customDomain !== options.where.customDomain) {
return false;
}
if (options.where.tenantId && d.tenantId !== options.where.tenantId) {
return false;
}
return true;
}) || null
);
}),
save: jest.fn(async (entity: TeamDomainEntity) => {
const idx = domains.findIndex(
d => (entity.id && d.id === entity.id) || d.customDomain === entity.customDomain
);
if (idx >= 0) {
domains[idx] = Object.assign(domains[idx], entity);
return domains[idx];
}
const saved = Object.assign(new TeamDomainEntity(), entity, {
id: nextId++,
createTime: new Date(),
updateTime: new Date(),
});
domains.push(saved);
return saved;
}),
};
service = new TeamDomainService();
service.teamDomainEntity = mockRepo as unknown as TeamDomainService['teamDomainEntity'];
});
it('缺少 tenantId 时抛出异常', async () => {
await expect(service.bindDomain(0, 'card.company.com')).rejects.toThrow(CoolCommException);
});
it('格式非法时阻断持久化并抛出异常', async () => {
await expect(service.bindDomain(1, 'http://bad-url.com:8080')).rejects.toThrow(
CoolCommException
);
});
it('域名已被其他租户占用时抛出防冲突异常', async () => {
await expect(service.bindDomain(99, 'taken.brand.com')).rejects.toThrow('已被其他企业绑定');
});
it('新租户首次绑定成功插入并保持 pending 状态', async () => {
const bound = await service.bindDomain(99, 'VIP.NewCompany.com', {
cnameTarget: 'cname.omnicard.ai',
});
expect(bound.id).toBeDefined();
expect(bound.tenantId).toBe(99);
expect(bound.customDomain).toBe('vip.newcompany.com');
expect(bound.sslStatus).toBe('pending');
expect(bound.cnameTarget).toBe('cname.omnicard.ai');
});
it('同一租户重复绑定或更新现有记录', async () => {
const bound1 = await service.bindDomain(88, 'taken.brand.com', {
cnameTarget: 'new-cname.omnicard.ai',
});
expect(bound1.id).toBe(1);
expect(bound1.cnameTarget).toBe('new-cname.omnicard.ai');
});
});
// ==========================================
// 6. 控制器接口测试 (AdminTeamDomainController)
// ==========================================
describe('6. 控制器接口调用测试 (AdminTeamDomainController)', () => {
let controller: AdminTeamDomainController;
let service: TeamDomainService;
beforeEach(() => {
service = new TeamDomainService();
service.validateDomainFormat = jest.fn((domain: string) => ({
valid: domain === 'card.example.com',
reason: domain === 'card.example.com' ? undefined : '域名格式非法',
}));
service.verifyDnsRecord = jest.fn(async (domain: string, targetCname: string) => ({
verified: true,
records: [targetCname],
targetCname,
}));
service.checkSslCertificate = jest.fn(async () => ({
valid: true,
status: 'active' as const,
daysRemaining: 60,
}));
service.bindDomain = jest.fn(async (tenantId: number, domain: string) => {
const entity = new TeamDomainEntity();
entity.id = 1;
entity.tenantId = tenantId;
entity.customDomain = domain;
entity.sslStatus = 'pending';
return entity;
});
controller = new AdminTeamDomainController();
controller.teamDomainService = service;
controller.ok = (data: unknown) => ({
code: 1000,
message: 'success',
data,
});
(
controller as unknown as { ctx: { admin: { userId: number; tenantId?: number } } }
).ctx = {
admin: { userId: 1, tenantId: 10 },
};
});
type MockApiResponse<T> = { code: number; message: string; data: T };
it('POST /validate 返回校验结果', async () => {
const res = (await controller.validate({ domain: 'card.example.com' })) as unknown as MockApiResponse<{ valid: boolean }>;
expect(res.code).toBe(1000);
expect(res.data.valid).toBe(true);
});
it('POST /verify-dns 返回 DNS CNAME 核验结果', async () => {
const res = (await controller.verifyDns({
domain: 'card.example.com',
targetCname: 'cname.omnicard.ai',
})) as unknown as MockApiResponse<{ verified: boolean }>;
expect(res.code).toBe(1000);
expect(res.data.verified).toBe(true);
});
it('POST /check-ssl 返回 SSL 状态', async () => {
const res = (await controller.checkSsl({ domain: 'card.example.com' })) as unknown as MockApiResponse<{ status: string }>;
expect(res.code).toBe(1000);
expect(res.data.status).toBe('active');
});
it('POST /bind 绑定企业独立二级域名', async () => {
const res = await controller.bind({ domain: 'card.example.com' });
expect(res.code).toBe(1000);
expect(service.bindDomain).toHaveBeenCalledWith(10, 'card.example.com', expect.any(Object));
});
});
});