文件
OmniCard/backend/test/modules/base/audit.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

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',
})
);
});
});
});