39 lines
3.1 KiB
JavaScript
39 lines
3.1 KiB
JavaScript
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'});
|
|
|
|
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);
|
|
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);
|
|
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',{});
|
|
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',{});
|
|
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/);
|
|
assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,1);
|
|
});
|