Files
chimera-Public/test/server/audit-log-service.test.js
T

54 lines
3.2 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const test = require('node:test');
const { createLogger } = require('../../src/platform/logger');
const { createAuditLogService, normalizeEntry } = require('../../src/services/audit-log-service');
test('audit center filters command events and exports redacted authenticated data', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-audit-'));
try {
const logger = createLogger(root, { maxBytes: 64 * 1024, maxFiles: 2, consoleOutput: false });
logger.audit('command', { command: 'recovery:restore', classification: 'destructive', ok: true, requestIp: '192.0.2.4', password: 'must-not-appear' });
logger.audit('command', { command: 'settings:set', classification: 'sensitive', ok: false, error: 'geweigerd' });
logger.info('server ready Authorization: should-never-leak');
const service = createAuditLogService({ logger });
const result = service.query({ level: 'audit', classification: 'destructive', result: 'success', search: 'recovery', limit: 10 });
assert.equal(result.entries.length, 1);
assert.equal(result.entries[0].command, 'recovery:restore');
assert.equal(result.entries[0].values[0].password, '[REDACTED]');
assert.equal(result.summary.destructive, 1);
const exported = service.exportAudit({ level: 'audit' }).toString('utf8');
assert.match(exported, /recovery:restore/);
assert.doesNotMatch(exported, /must-not-appear/);
assert.doesNotMatch(fs.readFileSync(logger.file, 'utf8'), /should-never-leak/);
assert.equal(service.health().exportAuthenticated, true);
logger.info({ oversized: 'z'.repeat(100_000) });
assert.ok(fs.statSync(logger.file).size < 64 * 1024);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
test('logger rotates bounded files and audit center rejects invalid filters', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-audit-rotate-'));
try {
const logger = createLogger(root, { maxBytes: 64 * 1024, maxFiles: 2, consoleOutput: false });
for (let index = 0; index < 180; index += 1) logger.audit('command', { command: `test:${index}`, classification: 'mutating', ok: true, note: 'x'.repeat(500) });
assert.equal(fs.existsSync(`${logger.file}.1`), true);
assert.equal(fs.existsSync(`${logger.file}.3`), false);
const service = createAuditLogService({ logger, maxReadBytes: 256 * 1024 });
assert.throws(() => service.query({ classification: 'root-shell' }), /Ongeldige auditclassificatie/);
assert.ok(service.query({ limit: 500 }).entries.length > 0);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
test('audit normalizer drops malformed timestamps and bounds untrusted fields', () => {
assert.equal(normalizeEntry({ time: 'invalid', level: 'audit', values: [] }), null);
const entry = normalizeEntry({ time: '2026-07-16T00:00:00Z', level: 'audit', values: [{ event: 'command', command: `x\n${'y'.repeat(300)}`, classification: 'destructive', ok: false }] });
assert.equal(entry.command.includes('\n'), false);
assert.equal(entry.command.length, 160);
assert.equal(entry.ok, false);
});