文件
OmniCard/backend/test/modules/card/cases.test.ts
wikiboss 6c3d5d323a refactor(arch): 落地平台架构演进第一二阶段整改,消除OOM与越权风险并支持高并发缓冲与AI多租户BYOK
- [P0安全底座] 多租户数据闭环:公开留资与雷达足迹自动逆向追溯绑定名片 tenantId 与归属销售 userId
- [P0性能稳定] 消除内存全量加载:公海池分批带条件扫描回收,雷达国家分布改用 SQL 原生 GROUP BY 聚合查询彻底消除 OOM 隐患
- [P0商业变现] 商业化配额门禁切面:在 Cool-Admin modifyBefore 生命周期织入 assertQuotaAvailable 校验,阻断超额名片创建
- [P0环境解耦] 客户端解耦本地硬编码:request.uts 支持 api_base_url 本地覆盖,生产数据库名修正为 crmmpdata
- [P1高并发削峰] 雷达足迹批量缓冲引擎:引入 CrmRadarBufferService 环形缓冲队列与阈值/定时批量 Bulk Insert 落盘
- [P2中枢租户化] AI 智能中枢多租户 BYOK 与 Token 成本风控:支持企业自带 Key 隔离、Token 消耗账本、用量配额门禁与名片人设个性化联动
- [跨端规范] 对齐 Uni-app x 移动微官网原生首屏与底部原生 tabBar 规范
- [测试驱动] 全工程 116 个测试套件,1,175 个单元与集成测试用例全部 100% 绿色通过,管理后台 Vite 打包无错误
2026-09-21 11:14:10 +08:00

208 行
7.8 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { BaseController } from '@cool-midway/core';
import { CardCasesEntity } from '../../../src/modules/card/entity/cases';
import { CardCasesService } from '../../../src/modules/card/service/cases';
import {
AdminCardCasesController,
adminCardCasesCurdOption,
} from '../../../src/modules/card/controller/admin/cases';
describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
const entityFile = path.join(
__dirname,
'../../../src/modules/card/entity/cases.ts'
);
const serviceFile = path.join(
__dirname,
'../../../src/modules/card/service/cases.ts'
);
const controllerFile = path.join(
__dirname,
'../../../src/modules/card/controller/admin/cases.ts'
);
describe('1. 契约文件与自动路由约定', () => {
it('文件路径应符合 /admin/card/cases 路由与模块约定', () => {
expect(fs.existsSync(entityFile)).toBe(true);
expect(fs.existsSync(serviceFile)).toBe(true);
expect(fs.existsSync(controllerFile)).toBe(true);
});
it('控制器应继承 BaseController 并挂载 CoolController CRUD', () => {
expect(AdminCardCasesController.prototype).toBeInstanceOf(BaseController);
expect(adminCardCasesCurdOption.entity).toBe(CardCasesEntity);
expect(adminCardCasesCurdOption.service).toBe(CardCasesService);
});
it('装饰器 api 必须包含 add/delete/update/info/list/page', () => {
expect(adminCardCasesCurdOption.api).toEqual([
'add',
'delete',
'update',
'info',
'list',
'page',
]);
});
it('pageQueryOp 应支持 a.cardId 精确筛选与 title/client/metrics 模糊搜索', () => {
const pageQueryOp = adminCardCasesCurdOption.pageQueryOp;
expect(pageQueryOp.fieldEq).toEqual(
expect.arrayContaining([{ column: 'a.card_id', requestParam: 'cardId' }])
);
expect(pageQueryOp.keyWordLikeFields).toEqual(
expect.arrayContaining(['a.title', 'a.client', 'a.metrics'])
);
expect(pageQueryOp.addOrderBy).toMatchObject({ sort_order: 'asc' });
});
});
describe('2. 实体字段契约 (CardCasesEntity)', () => {
it('实体实例应支持出海案例必需契约字段', () => {
const item = new CardCasesEntity();
item.cardId = 1001;
item.title = '智能电动滑板车欧美 DTC 出海';
item.client = 'VoltRide Global';
item.duration = '2024.01 - 2024.06';
item.metrics = '+320% ROI, 45,000+ 留资量';
item.coverImage = 'https://cdn.example.com/cases/voltride.webp';
item.pdfUrl = 'https://cdn.example.com/cases/voltride-deck.pdf';
item.description = '品牌从 0 到 1 进军北美市场独立站及全渠道获客';
item.sortOrder = 1;
expect(item.cardId).toBe(1001);
expect(item.title).toBe('智能电动滑板车欧美 DTC 出海');
expect(item.client).toBe('VoltRide Global');
expect(item.duration).toBe('2024.01 - 2024.06');
expect(item.metrics).toBe('+320% ROI, 45,000+ 留资量');
expect(item.coverImage).toBe('https://cdn.example.com/cases/voltride.webp');
expect(item.pdfUrl).toBe('https://cdn.example.com/cases/voltride-deck.pdf');
expect(item.description).toBe('品牌从 0 到 1 进军北美市场独立站及全渠道获客');
expect(item.sortOrder).toBe(1);
});
});
describe('3. 服务层按 cardId 筛选与 CRUD 逻辑 (CardCasesService)', () => {
let service: CardCasesService;
let mockList: CardCasesEntity[] = [];
beforeEach(() => {
mockList = [
Object.assign(new CardCasesEntity(), {
id: 1,
cardId: 101,
title: '跨境智能硬件众筹出海',
client: 'TechNova',
duration: '3个月',
metrics: '+280% 众筹目标达成',
coverImage: 'https://cdn.example.com/case1.png',
pdfUrl: 'https://cdn.example.com/case1.pdf',
sortOrder: 2,
}),
Object.assign(new CardCasesEntity(), {
id: 2,
cardId: 101,
title: '东南亚 TikTok 跨境直播获客',
client: 'FashionGo',
duration: '6个月',
metrics: '千万级曝光, +150% GMV',
coverImage: 'https://cdn.example.com/case2.png',
pdfUrl: 'https://cdn.example.com/case2.pdf',
sortOrder: 1,
}),
Object.assign(new CardCasesEntity(), {
id: 3,
cardId: 102,
title: 'B2B 重工机械中东拓客',
client: 'HeavyMach',
duration: '12个月',
metrics: '$5M+ 意向大单',
coverImage: 'https://cdn.example.com/case3.png',
pdfUrl: 'https://cdn.example.com/case3.pdf',
sortOrder: 1,
}),
];
service = new CardCasesService();
service.cardCasesEntity = {
find: jest.fn().mockImplementation(async (options?: { where?: { cardId?: number }; order?: Record<string, string> }) => {
let res = [...mockList];
if (options?.where?.cardId !== undefined) {
res = res.filter(item => Number(item.cardId) === Number(options.where.cardId));
}
if (options?.order?.sortOrder === 'ASC') {
res.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
}
return res;
}),
findOneBy: jest.fn().mockImplementation(async (where: { id: number }) => {
return mockList.find(item => item.id === where.id) || null;
}),
save: jest.fn().mockImplementation(async (entity: Partial<CardCasesEntity>) => {
if (entity.id) {
const index = mockList.findIndex(item => item.id === entity.id);
if (index >= 0) {
mockList[index] = Object.assign(mockList[index], entity);
return mockList[index];
}
}
const created = Object.assign(new CardCasesEntity(), entity, {
id: mockList.length + 1,
});
mockList.push(created);
return created;
}),
delete: jest.fn().mockImplementation(async (id: number) => {
mockList = mockList.filter(item => item.id !== id);
return { affected: 1 };
}),
} as unknown as typeof service.cardCasesEntity;
});
it('getCasesByCardId 应根据名片 cardId 筛选并按 sortOrder 升序排列', async () => {
const cases = await service.getCasesByCardId(101);
expect(cases).toHaveLength(2);
expect(cases[0].title).toBe('东南亚 TikTok 跨境直播获客');
expect(cases[0].sortOrder).toBe(1);
expect(cases[1].title).toBe('跨境智能硬件众筹出海');
expect(cases[1].sortOrder).toBe(2);
});
it('getCasesByCardId 传入无效 cardId 时应返回空数组', async () => {
const cases = await service.getCasesByCardId(0);
expect(cases).toEqual([]);
});
it('创建案例并能查询出最新案例', async () => {
const created = await service.cardCasesEntity.save({
cardId: 101,
title: '拉美电商本土化品牌出海',
client: 'LatamDirect',
duration: '4个月',
metrics: '+90% 复购率',
coverImage: 'https://cdn.example.com/case4.png',
pdfUrl: 'https://cdn.example.com/case4.pdf',
sortOrder: 3,
});
expect(created.id).toBeDefined();
expect(created.title).toBe('拉美电商本土化品牌出海');
const found = await service.cardCasesEntity.findOneBy({ id: created.id });
expect(found).not.toBeNull();
expect(found?.client).toBe('LatamDirect');
});
it('更新案例信息应成功', async () => {
await service.cardCasesEntity.save({
id: 1,
metrics: '+350% 众筹目标达成',
});
const updated = await service.cardCasesEntity.findOneBy({ id: 1 });
expect(updated?.metrics).toBe('+350% 众筹目标达成');
});
});
});