import { describe, expect, it } from "vitest"; import { SessionManager, type Session, type SessionStore } from "./sessions.js"; class MemSessions implements SessionStore { map = new Map(); listAll?: () => Promise; async put(s: Session) { this.map.set(s.id, s); } async get(id: string) { return this.map.get(id) ?? null; } async listByUser(userId: string) { return [...this.map.values()].filter((s) => s.userId === userId); } async delete(id: string) { this.map.delete(id); } } const base = { userId: "usr_1", refreshTokenHash: "h1", familyId: "f1" }; describe("SessionManager", () => { it("creates with defaults (30d)", async () => { const m = new SessionManager(new MemSessions()); const s = await m.create({ ...base, nowMs: 1000 }); expect(s.id.startsWith("sess_")).toBe(true); expect(s.expiresAt).toBe(1000 + 30 * 86_400_000); expect(s.lastActiveAt).toBe(1000); }); it("touches only live sessions", async () => { const store = new MemSessions(); const m = new SessionManager(store); const s = await m.create({ ...base, nowMs: 1000 }); const touched = await m.touch(s.id, 2000); expect(touched?.lastActiveAt).toBe(2000); await m.revoke(s.id, 3000); expect(await m.touch(s.id, 4000)).toBeNull(); }); it("revokes all for a user", async () => { const m = new SessionManager(new MemSessions()); await m.create({ ...base, nowMs: 1 }); await m.create({ ...base, refreshTokenHash: "h2", familyId: "f2", nowMs: 2 }); expect(await m.revokeAllForUser("usr_1", 100)).toBe(2); expect(await m.revokeAllForUser("usr_1", 101)).toBe(0); }); it("prunes revoked/expired when store supports listAll", async () => { const store = new MemSessions(); store.listAll = async () => [...store.map.values()]; const m = new SessionManager(store); const s = await m.create({ ...base, nowMs: 1, ttlMs: 100 }); await m.revoke(s.id, 50); expect(await m.pruneExpired(1000)).toBe(1); expect(store.map.size).toBe(0); }); });