Files

38 lines
3.1 KiB
JavaScript
Raw Permalink Normal View History

2026-10-13 10:33:31 +02:00
import test from 'node:test';
import assert from 'node:assert/strict';
import {mkdtempSync,rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {openStore} from '../apps/server/store.mjs';
import {pruneAuditEvents,startAuditRetention} from '../apps/server/audit-retention.mjs';
function fixture(t){const directory=mkdtempSync(join(tmpdir(),'safetynet-retention-')),store=openStore(directory);t.after(()=>{store.db.close();rmSync(directory,{recursive:true,force:true});});return store;}
test('retention preserves pending export, incident evidence and replay deduplication',t=>{
const s=fixture(t);s.db.exec('CREATE TABLE IF NOT EXISTS incident_cases(id TEXT PRIMARY KEY,event_id INTEGER REFERENCES events(id))');
for(let i=0;i<4;i++)s.event('device','dlp.block',{ruleId:'rule'});
2026-10-13 10:33:31 +02:00
s.db.prepare('INSERT INTO incident_cases(id,event_id) VALUES(?,?)').run('case',2);
s.db.prepare('INSERT INTO device_events VALUES(?,?)').run('device','replay-id');
const p=pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,exportedThrough:3});assert.equal(p.eligible,2);assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,4);
const r=pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,exportedThrough:3,dryRun:false});assert.equal(r.eligible,2);
2026-10-13 10:33:31 +02:00
assert.deepEqual(s.db.prepare('SELECT id FROM events ORDER BY id').all().map(x=>x.id),[2,4,5]);
assert.equal(s.db.prepare('SELECT count(*) n FROM device_events').get().n,1);
assert.equal(s.db.prepare('SELECT kind FROM events WHERE id=5').get().kind,'audit.retention-applied');
});
test('batch and SIEM boundaries never orphan a Sigma correlation',t=>{
const s=fixture(t),p=s.get('policy');p.security.sigma.rules=[{id:'r',title:'Rule',level:'high',detection:{selection:{kind:'dlp.block'},condition:'selection'}}];s.put('policy',p);s.event('d','dlp.block',{});
assert.equal(pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,limit:1,exportedThrough:2,dryRun:false}).eligible,0);
assert.equal(pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,exportedThrough:1,dryRun:false}).eligible,0);
assert.equal(pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,exportedThrough:2,dryRun:false}).eligible,2);
2026-10-13 10:33:31 +02:00
assert.equal(s.db.prepare('SELECT count(*) n FROM alerts').get().n,0);
});
test('missing cursor fails closed and retention requires explicit deployment opt-in',t=>{
const s=fixture(t);s.event(null,'policy.published',{});
2026-10-13 10:33:31 +02:00
assert.throws(()=>pruneAuditEvents(s,{days:30}),/cursor/);
const off=startAuditRetention(s,{status:()=>({cursor:1})});off.run();assert.equal(off.status().enabled,false);assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,1);off.stop();
});
test('failed deletion rolls back the whole retention operation',t=>{
const s=fixture(t);s.event(null,'policy.published',{});
2026-10-13 10:33:31 +02:00
s.db.exec("CREATE TRIGGER retention_reject BEFORE INSERT ON events WHEN NEW.kind='audit.retention-applied' BEGIN SELECT RAISE(ABORT,'fixture'); END");
assert.throws(()=>pruneAuditEvents(s,{now:Date.now()+60*86400000,days:30,exportedThrough:1,dryRun:false}),/fixture/);
2026-10-13 10:33:31 +02:00
assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,1);
});