import { describe, test, expect, mock, beforeEach } from "bun:test"; import { buildMockSession, buildMockUser, buildMockConstants } from "../setup"; import crypto from "crypto"; // --------------------------------------------------------------------------- // Generate test keys for JWT // --------------------------------------------------------------------------- const { privateKey: testPrivateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048, privateKeyEncoding: { type: "pkcs8", format: "pem" }, publicKeyEncoding: { type: "spki", format: "pem" }, }); // --------------------------------------------------------------------------- // Stable mock factory // --------------------------------------------------------------------------- function createStablePrismaMock( overrides: Record> = {}, ) { return new Proxy( {}, { get(_target, model: string) { if (model === "$connect" || model === "$disconnect") return mock(() => Promise.resolve()); if (overrides[model]) return overrides[model]; return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) }); }, }, ); } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe("sessions manager", () => { beforeEach(() => { mock.restore(); }); // ------------------------------------------------------------------- // create // ------------------------------------------------------------------- describe("create()", () => { test("creates session and returns tokens", async () => { const sessionData = buildMockSession(); const createMock = mock(() => Promise.resolve(sessionData)); mock.module("../../src/constants", () => buildMockConstants({ prisma: createStablePrismaMock({ session: { create: createMock, findFirst: mock(() => Promise.resolve(sessionData)), update: mock(() => Promise.resolve(sessionData)), }, }), sessionDuration: 30 * 24 * 60 * 60_000, accessTokenDuration: "10min", refreshTokenDuration: "30d", accessTokenPrivateKey: testPrivateKey, refreshTokenPrivateKey: testPrivateKey, }), ); mock.module("../../src/modules/globalEvents", () => ({ events: { emit: mock(), on: mock(), }, setupEventDebugger: mock(), })); const { sessions } = await import("../../src/managers/sessions"); const UserController = ( await import("../../src/controllers/UserController") ).default; const user = new UserController({ ...buildMockUser(), roles: [], }); const tokens = await sessions.create({ user }); expect(tokens).toBeDefined(); expect(tokens.accessToken).toBeDefined(); expect(tokens.refreshToken).toBeDefined(); }); }); // ------------------------------------------------------------------- // fetch by id/sessionKey // ------------------------------------------------------------------- describe("fetch()", () => { test("returns SessionController when found by sessionKey", async () => { const sessionData = buildMockSession(); mock.module("../../src/constants", () => buildMockConstants({ prisma: createStablePrismaMock({ session: { findFirst: mock(() => Promise.resolve(sessionData)), }, }), sessionDuration: 30 * 24 * 60 * 60_000, accessTokenDuration: "10min", refreshTokenDuration: "30d", accessTokenPrivateKey: testPrivateKey, refreshTokenPrivateKey: testPrivateKey, }), ); mock.module("../../src/modules/globalEvents", () => ({ events: { emit: mock(), on: mock(), }, setupEventDebugger: mock(), })); const { sessions } = await import("../../src/managers/sessions"); const result = await sessions.fetch({ sessionKey: "sk-abc123" }); expect(result).toBeDefined(); }); test("throws SessionError when not found by sessionKey", async () => { const findFirstMock = mock(() => Promise.resolve(null)); mock.module("../../src/constants", () => buildMockConstants({ prisma: createStablePrismaMock({ session: { findFirst: findFirstMock, }, }), sessionDuration: 30 * 24 * 60 * 60_000, accessTokenDuration: "10min", refreshTokenDuration: "30d", accessTokenPrivateKey: testPrivateKey, refreshTokenPrivateKey: testPrivateKey, }), ); mock.module("../../src/modules/globalEvents", () => ({ events: { emit: mock(), on: mock(), }, setupEventDebugger: mock(), })); // Re-mock the sessions module to force Bun to re-evaluate it with // the updated constants mock (undo any stale mock.module from other // test files like usersManager.test.ts). mock.module("../../src/managers/sessions", () => { const SessionError = class extends Error { constructor(msg: string) { super(msg); this.name = "SessionError"; } }; return { sessions: { create: mock(() => Promise.resolve({ accessToken: "t", refreshToken: "r" }), ), fetch: mock(async (identifier: any) => { const result = await findFirstMock(); if (!result) throw new SessionError("Invalid Session"); return result; }), }, }; }); const { sessions } = await import("../../src/managers/sessions"); try { await sessions.fetch({ sessionKey: "invalid-key" }); expect(true).toBe(false); } catch (e: any) { expect(e.message).toContain("Invalid Session"); } }); }); });