// Tests for session retention — enforceMaxSessions logic. // Uses bun:test. Tests the retention count + age pruning from agent-store.svelte.ts. import { describe, it, expect, beforeEach } from 'bun:test'; // ── Replicated types and retention logic ──────────────────────────────────── interface SessionEntry { sessionId: string; projectId: string; status: 'idle' | 'running' | 'done' | 'error'; lastMessageTs: number; } interface RetentionConfig { count: number; days: number; } function setRetentionConfig(count: number, days: number): RetentionConfig { return { count: Math.max(1, Math.min(50, count)), days: Math.max(1, Math.min(365, days)), }; } function enforceMaxSessions( sessions: SessionEntry[], projectId: string, config: RetentionConfig, ): string[] { const now = Date.now(); const maxAgeMs = config.days * 24 * 60 * 60 * 1000; // Filter to this project's non-running sessions, sorted newest first const projectSessions = sessions .filter(s => s.projectId === projectId && s.status !== 'running') .sort((a, b) => b.lastMessageTs - a.lastMessageTs); const toPurge: string[] = []; // Prune by count if (projectSessions.length > config.count) { const excess = projectSessions.slice(config.count); for (const s of excess) toPurge.push(s.sessionId); } // Prune by age for (const s of projectSessions) { if (s.lastMessageTs > 0 && (now - s.lastMessageTs) > maxAgeMs) { if (!toPurge.includes(s.sessionId)) toPurge.push(s.sessionId); } } return toPurge; } // ── Tests ─────────────────────────────────────────────────────────────────── describe('setRetentionConfig', () => { it('clamps count to [1, 50]', () => { expect(setRetentionConfig(0, 30).count).toBe(1); expect(setRetentionConfig(100, 30).count).toBe(50); expect(setRetentionConfig(5, 30).count).toBe(5); }); it('clamps days to [1, 365]', () => { expect(setRetentionConfig(5, 0).days).toBe(1); expect(setRetentionConfig(5, 500).days).toBe(365); expect(setRetentionConfig(5, 30).days).toBe(30); }); }); describe('enforceMaxSessions — count-based pruning', () => { it('keeps only N most recent sessions', () => { const now = Date.now(); const sessions: SessionEntry[] = [ { sessionId: 's1', projectId: 'p1', status: 'done', lastMessageTs: now - 50000 }, { sessionId: 's2', projectId: 'p1', status: 'done', lastMessageTs: now - 40000 }, { sessionId: 's3', projectId: 'p1', status: 'done', lastMessageTs: now - 30000 }, { sessionId: 's4', projectId: 'p1', status: 'done', lastMessageTs: now - 20000 }, { sessionId: 's5', projectId: 'p1', status: 'done', lastMessageTs: now - 10000 }, ]; const config: RetentionConfig = { count: 3, days: 365 }; const toPurge = enforceMaxSessions(sessions, 'p1', config); expect(toPurge).toHaveLength(2); // s1 and s2 are oldest expect(toPurge).toContain('s1'); expect(toPurge).toContain('s2'); }); it('does not purge when under limit', () => { const now = Date.now(); const sessions: SessionEntry[] = [ { sessionId: 's1', projectId: 'p1', status: 'done', lastMessageTs: now }, ]; const config: RetentionConfig = { count: 5, days: 365 }; expect(enforceMaxSessions(sessions, 'p1', config)).toHaveLength(0); }); }); describe('enforceMaxSessions — age-based pruning', () => { it('prunes sessions older than retention days', () => { const now = Date.now(); const oldTs = now - (31 * 24 * 60 * 60 * 1000); // 31 days ago const sessions: SessionEntry[] = [ { sessionId: 's-old', projectId: 'p1', status: 'done', lastMessageTs: oldTs }, { sessionId: 's-new', projectId: 'p1', status: 'done', lastMessageTs: now }, ]; const config: RetentionConfig = { count: 10, days: 30 }; const toPurge = enforceMaxSessions(sessions, 'p1', config); expect(toPurge).toEqual(['s-old']); }); it('keeps sessions within retention window', () => { const now = Date.now(); const recentTs = now - (5 * 24 * 60 * 60 * 1000); // 5 days ago const sessions: SessionEntry[] = [ { sessionId: 's1', projectId: 'p1', status: 'done', lastMessageTs: recentTs }, ]; const config: RetentionConfig = { count: 10, days: 30 }; expect(enforceMaxSessions(sessions, 'p1', config)).toHaveLength(0); }); }); describe('enforceMaxSessions — running sessions protected', () => { it('never purges running sessions', () => { const now = Date.now(); const sessions: SessionEntry[] = [ { sessionId: 's-running', projectId: 'p1', status: 'running', lastMessageTs: now - 999999999 }, { sessionId: 's-done', projectId: 'p1', status: 'done', lastMessageTs: now }, ]; const config: RetentionConfig = { count: 1, days: 1 }; const toPurge = enforceMaxSessions(sessions, 'p1', config); expect(toPurge).not.toContain('s-running'); }); }); describe('enforceMaxSessions — project isolation', () => { it('only prunes sessions for the specified project', () => { const now = Date.now(); const sessions: SessionEntry[] = [ { sessionId: 'p1-s1', projectId: 'p1', status: 'done', lastMessageTs: now - 1000 }, { sessionId: 'p2-s1', projectId: 'p2', status: 'done', lastMessageTs: now - 1000 }, ]; const config: RetentionConfig = { count: 0, days: 365 }; // count 0 → clamped to 1 const actualConfig = setRetentionConfig(0, 365); const toPurge = enforceMaxSessions(sessions, 'p1', actualConfig); expect(toPurge).not.toContain('p2-s1'); }); });