85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { BadRequestException } from '@nestjs/common';
|
|
import type { DataSource, EntityManager } from 'typeorm';
|
|
import { AuditQueryService } from '../../src/audit/audit-query.service';
|
|
import { buildAuditFilters } from '../../src/audit/audit-query';
|
|
import { ListAuditQueryDto } from '../../src/audit/dto/list-audit-query.dto';
|
|
import { AuditSource } from '../../src/database/entities';
|
|
|
|
test('buildAuditFilters parameterizes every audit filter', () => {
|
|
const query = Object.assign(new ListAuditQueryDto(), {
|
|
actorUsername: 'admin',
|
|
action: 'USER_CREATED',
|
|
source: AuditSource.WEB,
|
|
entityType: 'user',
|
|
requestId: 'request-12345678',
|
|
from: '2026-08-01T00:00:00.000Z',
|
|
to: '2026-08-31T23:59:59.999Z',
|
|
search: "anything' OR 1=1 --",
|
|
});
|
|
|
|
const filters = buildAuditFilters(query);
|
|
assert.match(filters.clause, /event\.actor_username ILIKE \$1/);
|
|
assert.match(filters.clause, /event\.action = \$2/);
|
|
assert.match(filters.clause, /event\.source = \$3/);
|
|
assert.match(filters.clause, /event\.occurred_at >= \$6/);
|
|
assert.match(filters.clause, /event\.occurred_at <= \$7/);
|
|
assert.match(filters.clause, /event\.request_id ILIKE \$8/);
|
|
assert.equal(filters.clause.includes("anything' OR 1=1"), false);
|
|
assert.equal(filters.parameters[7], "%anything' OR 1=1 --%");
|
|
});
|
|
|
|
test('AuditQueryService rejects an inverted date range before querying', async () => {
|
|
const dataSource = {
|
|
transaction: () => {
|
|
throw new Error('transaction should not run');
|
|
},
|
|
} as unknown as DataSource;
|
|
const service = new AuditQueryService(dataSource);
|
|
const query = Object.assign(new ListAuditQueryDto(), {
|
|
from: '2026-08-31T00:00:00.000Z',
|
|
to: '2026-08-01T00:00:00.000Z',
|
|
});
|
|
|
|
await assert.rejects(
|
|
service.list(query),
|
|
(error: unknown) =>
|
|
error instanceof BadRequestException && error.getStatus() === 400,
|
|
);
|
|
});
|
|
|
|
test('AuditQueryService returns stable pagination metadata', async () => {
|
|
let calls = 0;
|
|
const manager = {
|
|
query: async () => {
|
|
calls += 1;
|
|
return calls === 1
|
|
? [{ total: 26 }]
|
|
: [
|
|
{
|
|
id: '0f45472b-541b-4d6c-969f-0821463d0ea7',
|
|
action: 'USER_CREATED',
|
|
},
|
|
];
|
|
},
|
|
} as unknown as EntityManager;
|
|
const dataSource = {
|
|
transaction: async (
|
|
_isolation: string,
|
|
operation: (entityManager: EntityManager) => Promise<unknown>,
|
|
) => operation(manager),
|
|
} as unknown as DataSource;
|
|
const service = new AuditQueryService(dataSource);
|
|
const query = Object.assign(new ListAuditQueryDto(), {
|
|
page: 2,
|
|
pageSize: 25,
|
|
});
|
|
|
|
const result = await service.list(query);
|
|
assert.equal(result.meta.total, 26);
|
|
assert.equal(result.meta.totalPages, 2);
|
|
assert.equal(result.meta.page, 2);
|
|
assert.equal(result.data.length, 1);
|
|
});
|