- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
229 行
7.9 KiB
TypeScript
229 行
7.9 KiB
TypeScript
import { getMetadataArgsStorage } from 'typeorm';
|
|
import { BaseEntity } from '../../../src/modules/base/entity/base';
|
|
import { BaseAuditLogEntity } from '../../../src/modules/base/entity/audit';
|
|
import { BaseAuditLogService } from '../../../src/modules/base/service/audit';
|
|
import { BaseAuditLogController } from '../../../src/modules/base/controller/admin/audit';
|
|
|
|
describe('Point 20: 后台系统安全审计日志与行为追踪页面 单元测试', () => {
|
|
const metadata = getMetadataArgsStorage();
|
|
|
|
// ==========================================
|
|
// 1. 实体定义与数据库字段映射测试
|
|
// ==========================================
|
|
describe('1. 实体定义与数据库表映射测试', () => {
|
|
it('BaseAuditLogEntity 应映射至 base_audit_log 表且继承 BaseEntity', () => {
|
|
const table = metadata.tables.find((t) => t.target === BaseAuditLogEntity);
|
|
expect(table).toBeDefined();
|
|
expect(table?.name).toBe('base_audit_log');
|
|
expect(BaseAuditLogEntity.prototype instanceof BaseEntity).toBe(true);
|
|
});
|
|
|
|
it('BaseAuditLogEntity 应包含所有合规审计字段映射', () => {
|
|
const columns = metadata.columns.filter((c) => c.target === BaseAuditLogEntity);
|
|
const expected: Record<string, string> = {
|
|
module: 'module',
|
|
action: 'action',
|
|
operatorId: 'operator_id',
|
|
operatorName: 'operator_name',
|
|
ip: 'ip',
|
|
detail: 'detail',
|
|
status: 'status',
|
|
};
|
|
|
|
for (const [prop, dbCol] of Object.entries(expected)) {
|
|
const col = columns.find((c) => c.propertyName === prop);
|
|
expect(col).toBeDefined();
|
|
expect(col?.options.name || col?.propertyName).toBe(dbCol);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ==========================================
|
|
// 2. 服务核心业务与自动落库测试 (BaseAuditLogService)
|
|
// ==========================================
|
|
describe('2. 审计日志自动记录与多条件过滤测试 (BaseAuditLogService)', () => {
|
|
let service: BaseAuditLogService;
|
|
let logs: BaseAuditLogEntity[];
|
|
let nextId = 1;
|
|
|
|
beforeEach(() => {
|
|
logs = [];
|
|
nextId = 1;
|
|
|
|
// Mock Repository
|
|
const mockRepo = {
|
|
save: jest.fn(async (entity: BaseAuditLogEntity) => {
|
|
const saved = Object.assign(new BaseAuditLogEntity(), entity, {
|
|
id: entity.id || nextId++,
|
|
createTime: entity.createTime || new Date(),
|
|
updateTime: new Date(),
|
|
status: entity.status !== undefined ? entity.status : 1,
|
|
});
|
|
const existingIdx = logs.findIndex((l) => l.id === saved.id);
|
|
if (existingIdx >= 0) {
|
|
logs[existingIdx] = saved;
|
|
} else {
|
|
logs.push(saved);
|
|
}
|
|
return saved;
|
|
}),
|
|
insert: jest.fn(async (entity: BaseAuditLogEntity) => {
|
|
const saved = Object.assign(new BaseAuditLogEntity(), entity, {
|
|
id: nextId++,
|
|
createTime: new Date(),
|
|
updateTime: new Date(),
|
|
status: entity.status !== undefined ? entity.status : 1,
|
|
});
|
|
logs.push(saved);
|
|
return { identifiers: [{ id: saved.id }] };
|
|
}),
|
|
findAndCount: jest.fn(async (options?: {
|
|
where?: Record<string, unknown>;
|
|
skip?: number;
|
|
take?: number;
|
|
order?: Record<string, 'ASC' | 'DESC'>;
|
|
}) => {
|
|
let filtered = [...logs];
|
|
if (options?.where) {
|
|
filtered = filtered.filter((item) => {
|
|
const record = item as unknown as Record<string, unknown>;
|
|
for (const [k, v] of Object.entries(options.where!)) {
|
|
if (v !== undefined && record[k] !== v) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
const skip = options?.skip || 0;
|
|
const take = options?.take || 20;
|
|
return [filtered.slice(skip, skip + take), filtered.length];
|
|
}),
|
|
};
|
|
|
|
service = new BaseAuditLogService();
|
|
service.baseAuditLogEntity = mockRepo as unknown as BaseAuditLogService['baseAuditLogEntity'];
|
|
});
|
|
|
|
it('record 应能自动落库审计日志,默认 status 为 1', async () => {
|
|
const created = await service.record({
|
|
module: 'auth',
|
|
action: 'login',
|
|
operatorId: 10,
|
|
operatorName: 'SecAdmin',
|
|
ip: '192.168.1.100',
|
|
detail: JSON.stringify({ browser: 'Chrome', os: 'Windows' }),
|
|
});
|
|
|
|
expect(created.id).toBeDefined();
|
|
expect(created.module).toBe('auth');
|
|
expect(created.action).toBe('login');
|
|
expect(created.operatorId).toBe(10);
|
|
expect(created.operatorName).toBe('SecAdmin');
|
|
expect(created.ip).toBe('192.168.1.100');
|
|
expect(created.status).toBe(1);
|
|
expect(logs.length).toBe(1);
|
|
});
|
|
|
|
it('record 支持显式指定失败状态 status=0', async () => {
|
|
const created = await service.record({
|
|
module: 'nfc',
|
|
action: 'batch_export',
|
|
operatorId: 12,
|
|
operatorName: 'TestUser',
|
|
ip: '10.0.0.1',
|
|
detail: 'Permission denied for batch NFC export',
|
|
status: 0,
|
|
});
|
|
|
|
expect(created.status).toBe(0);
|
|
expect(created.module).toBe('nfc');
|
|
expect(logs[0].status).toBe(0);
|
|
});
|
|
|
|
it('filterLogs 应支持按 module, status 及分页多条件过滤', async () => {
|
|
// 预置多条审计日志
|
|
await service.record({
|
|
module: 'auth',
|
|
action: 'login',
|
|
operatorId: 1,
|
|
operatorName: 'Alice',
|
|
ip: '127.0.0.1',
|
|
status: 1,
|
|
});
|
|
await service.record({
|
|
module: 'auth',
|
|
action: 'logout',
|
|
operatorId: 1,
|
|
operatorName: 'Alice',
|
|
ip: '127.0.0.1',
|
|
status: 1,
|
|
});
|
|
await service.record({
|
|
module: 'nfc',
|
|
action: 'format_card',
|
|
operatorId: 2,
|
|
operatorName: 'Bob',
|
|
ip: '192.168.1.20',
|
|
status: 0,
|
|
});
|
|
|
|
// 1. 按 module 过滤
|
|
const authLogs = await service.filterLogs({ module: 'auth', page: 1, size: 10 });
|
|
expect(authLogs.total).toBe(2);
|
|
expect(authLogs.list.every((l) => l.module === 'auth')).toBe(true);
|
|
|
|
// 2. 按 status 过滤
|
|
const failedLogs = await service.filterLogs({ status: 0, page: 1, size: 10 });
|
|
expect(failedLogs.total).toBe(1);
|
|
expect(failedLogs.list[0].action).toBe('format_card');
|
|
|
|
// 3. 关键字模糊搜索
|
|
const bobLogs = await service.filterLogs({ keyWord: 'Bob', page: 1, size: 10 });
|
|
expect(bobLogs.total).toBe(1);
|
|
expect(bobLogs.list[0].operatorName).toBe('Bob');
|
|
});
|
|
});
|
|
|
|
// ==========================================
|
|
// 3. 控制器契约与接口测试 (BaseAuditLogController)
|
|
// ==========================================
|
|
describe('3. 控制器路由与接口测试 (BaseAuditLogController)', () => {
|
|
let controller: BaseAuditLogController;
|
|
let auditService: BaseAuditLogService;
|
|
|
|
beforeEach(() => {
|
|
auditService = new BaseAuditLogService();
|
|
auditService.record = jest.fn(async (params) => {
|
|
const entity = new BaseAuditLogEntity();
|
|
Object.assign(entity, params, { id: 99, createTime: new Date() });
|
|
return entity;
|
|
});
|
|
|
|
controller = new BaseAuditLogController();
|
|
controller.baseAuditLogService = auditService;
|
|
controller.ok = (data: unknown) => ({
|
|
code: 1000,
|
|
message: 'success',
|
|
data,
|
|
});
|
|
});
|
|
|
|
it('控制器应提供 record 方法并返回 standard 1000 格式', async () => {
|
|
expect(typeof controller.record).toBe('function');
|
|
const res = await controller.record({
|
|
module: 'team',
|
|
action: 'role_update',
|
|
operatorName: 'SuperAdmin',
|
|
});
|
|
expect(res.code).toBe(1000);
|
|
expect(auditService.record).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
module: 'team',
|
|
action: 'role_update',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|