import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { createInMemorySessionStore } from "./session.js"; describe("acp session manager", () => { let nowMs = 0; const now = () => nowMs; const advance = (ms: number) => { nowMs += ms; }; let store = createInMemorySessionStore({ now }); beforeEach(() => { nowMs = 1_000; store = createInMemorySessionStore({ now }); }); afterEach(() => { store.clearAllSessionsForTest(); }); it("tracks active runs and clears on cancel", () => { const session = store.createSession({ sessionKey: "acp:test", cwd: "/tmp", }); const controller = new AbortController(); store.setActiveRun(session.sessionId, "run-1", controller); expect(store.getSessionByRunId("run-1")?.sessionId).toBe(session.sessionId); const cancelled = store.cancelActiveRun(session.sessionId); expect(cancelled).toBe(true); expect(store.getSessionByRunId("run-1")).toBeUndefined(); }); it("refreshes existing session IDs instead of creating duplicates", () => { const first = store.createSession({ sessionId: "existing", sessionKey: "acp:one", cwd: "/tmp/one", }); advance(500); const refreshed = store.createSession({ sessionId: "existing", sessionKey: "acp:two", cwd: "/tmp/two", }); expect(refreshed).toBe(first); expect(refreshed.sessionKey).toBe("acp:two"); expect(refreshed.cwd).toBe("/tmp/two"); expect(refreshed.createdAt).toBe(1_000); expect(refreshed.lastTouchedAt).toBe(1_500); expect(store.hasSession("existing")).toBe(true); }); it("reaps idle sessions before enforcing the max session cap", () => { const boundedStore = createInMemorySessionStore({ maxSessions: 1, idleTtlMs: 1_000, now, }); try { boundedStore.createSession({ sessionId: "old", sessionKey: "acp:old", cwd: "/tmp", }); advance(2_000); const fresh = boundedStore.createSession({ sessionId: "fresh", sessionKey: "acp:fresh", cwd: "/tmp", }); expect(fresh.sessionId).toBe("fresh"); expect(boundedStore.getSession("old")).toBeUndefined(); expect(boundedStore.hasSession("old")).toBe(false); } finally { boundedStore.clearAllSessionsForTest(); } }); it("uses soft-cap eviction for the oldest idle session when full", () => { const boundedStore = createInMemorySessionStore({ maxSessions: 2, idleTtlMs: 24 * 60 * 60 * 1_000, now, }); try { const first = boundedStore.createSession({ sessionId: "first", sessionKey: "acp:first", cwd: "/tmp", }); advance(100); const second = boundedStore.createSession({ sessionId: "second", sessionKey: "acp:second", cwd: "/tmp", }); const controller = new AbortController(); boundedStore.setActiveRun(second.sessionId, "run-2", controller); advance(100); const third = boundedStore.createSession({ sessionId: "third", sessionKey: "acp:third", cwd: "/tmp", }); expect(third.sessionId).toBe("third"); expect(boundedStore.getSession(first.sessionId)).toBeUndefined(); expect(boundedStore.getSession(second.sessionId)).toBeDefined(); } finally { boundedStore.clearAllSessionsForTest(); } }); it("rejects when full and no session is evictable", () => { const boundedStore = createInMemorySessionStore({ maxSessions: 1, idleTtlMs: 24 * 60 * 60 * 1_000, now, }); try { const only = boundedStore.createSession({ sessionId: "only", sessionKey: "acp:only", cwd: "/tmp", }); boundedStore.setActiveRun(only.sessionId, "run-only", new AbortController()); expect(() => boundedStore.createSession({ sessionId: "next", sessionKey: "acp:next", cwd: "/tmp", }), ).toThrow(/session limit reached/i); } finally { boundedStore.clearAllSessionsForTest(); } }); });