/** * Unit tests for SessionRepository and AnomalyRepository using in-memory SQLite. */ import Database from 'better-sqlite3'; import { runMigrations } from '../../src/db/migrations'; import { SessionRepository } from '../../src/db/SessionRepository'; import { AnomalyRepository } from '../../src/db/AnomalyRepository'; import { IAnomaly } from '../../src/core/interfaces'; function makeDb(): Database.Database { const db = new Database(':memory:'); db.pragma('foreign_keys = ON'); runMigrations(db); return db; } function makeAnomaly(id: string): IAnomaly { return { id, type: 'http_error', severity: 'high', observationId: 'obs1', actionTrace: [], description: 'Test anomaly', evidence: { rawErrors: ['500 error'] }, timestamp: Date.now(), }; } describe('SessionRepository', () => { let db: Database.Database; let repo: SessionRepository; beforeEach(() => { db = makeDb(); repo = new SessionRepository(db); }); afterEach(() => db.close()); it('creates and retrieves a session', () => { repo.create({ id: 'sess1', url: 'http://x.com', seed: 42, maxStates: 50, startedAt: 1000 }); const row = repo.findById('sess1'); expect(row).toBeDefined(); expect(row!.url).toBe('http://x.com'); expect(row!.seed).toBe(42); expect(row!.status).toBe('running'); }); it('returns undefined for unknown id', () => { expect(repo.findById('nope')).toBeUndefined(); }); it('findAll returns all sessions', () => { repo.create({ id: 's1', url: 'http://a.com', seed: 1, maxStates: 10, startedAt: 1000 }); repo.create({ id: 's2', url: 'http://b.com', seed: 2, maxStates: 10, startedAt: 2000 }); expect(repo.findAll()).toHaveLength(2); }); it('updates fields', () => { repo.create({ id: 's1', url: 'http://a.com', seed: 1, maxStates: 10, startedAt: 1000 }); repo.update('s1', { status: 'completed', statesVisited: 5, anomaliesFound: 2, finishedAt: 9999 }); const row = repo.findById('s1')!; expect(row.status).toBe('completed'); expect(row.states_visited).toBe(5); expect(row.anomalies_found).toBe(2); expect(row.finished_at).toBe(9999); }); it('deletes a session', () => { repo.create({ id: 's1', url: 'http://a.com', seed: 1, maxStates: 10, startedAt: 1000 }); repo.delete('s1'); expect(repo.findById('s1')).toBeUndefined(); }); }); describe('AnomalyRepository', () => { let db: Database.Database; let sessionRepo: SessionRepository; let anomalyRepo: AnomalyRepository; beforeEach(() => { db = makeDb(); sessionRepo = new SessionRepository(db); anomalyRepo = new AnomalyRepository(db); sessionRepo.create({ id: 'sess1', url: 'http://x.com', seed: 42, maxStates: 50, startedAt: 1000 }); sessionRepo.create({ id: 'sess2', url: 'http://y.com', seed: 43, maxStates: 50, startedAt: 2000 }); }); afterEach(() => db.close()); it('creates and retrieves an anomaly', () => { const a = makeAnomaly('anom1'); anomalyRepo.create(a, 'sess1'); const found = anomalyRepo.findById('anom1'); expect(found).toBeDefined(); expect(found!.id).toBe('anom1'); expect(found!.sessionId).toBe('sess1'); expect(found!.severity).toBe('high'); }); it('returns undefined for unknown id', () => { expect(anomalyRepo.findById('nope')).toBeUndefined(); }); it('findAll with sessionId filter', () => { anomalyRepo.create(makeAnomaly('a1'), 'sess1'); anomalyRepo.create(makeAnomaly('a2'), 'sess2'); const result = anomalyRepo.findAll({ sessionId: 'sess1' }); expect(result).toHaveLength(1); expect(result[0]!.id).toBe('a1'); }); it('findAll with severity filter', () => { const low = { ...makeAnomaly('a1'), severity: 'low' as const }; const high = { ...makeAnomaly('a2'), severity: 'high' as const }; anomalyRepo.create(low, 'sess1'); anomalyRepo.create(high, 'sess1'); const result = anomalyRepo.findAll({ severity: 'low' }); expect(result).toHaveLength(1); expect(result[0]!.id).toBe('a1'); }); it('count returns total', () => { anomalyRepo.create(makeAnomaly('a1'), 'sess1'); anomalyRepo.create(makeAnomaly('a2'), 'sess2'); expect(anomalyRepo.count()).toBe(2); }); it('countBySeverity returns correct count', () => { const high = { ...makeAnomaly('a1'), severity: 'high' as const }; const critical = { ...makeAnomaly('a2'), severity: 'critical' as const }; const low = { ...makeAnomaly('a3'), severity: 'low' as const }; anomalyRepo.create(high, 'sess1'); anomalyRepo.create(critical, 'sess1'); anomalyRepo.create(low, 'sess1'); expect(anomalyRepo.countBySeverity(['high', 'critical'])).toBe(2); }); });