import { jest } from "@jest/globals"; import express from "express"; import request from "supertest"; // Manual mocks: the API-key verifier and the DB pool. No real database, no scrypt // — apiMiddleware's branching is what we exercise here. const verifyKey = jest.fn(); const query = jest.fn(); const release = jest.fn(async () => {}); jest.unstable_mockModule("../modules/core/apiKey.js", () => ({ verifyKey })); jest.unstable_mockModule("../db/index.js", () => ({ getPool: async () => ({ query, release }) })); const { apiMiddleware } = await import("./auth.js"); function makeApp() { const app = express(); app.use((req, _res, next) => { req.session = {}; next(); }); app.get("/probe", apiMiddleware, (req, res) => res.json({ userId: req.session.user_id })); return app; } beforeEach(() => { verifyKey.mockReset(); query.mockReset(); release.mockClear(); }); describe("apiMiddleware", () => { it("authenticates a valid hashed key and sets req.session.user_id (no legacy lookup)", async () => { verifyKey.mockResolvedValue({ user_id: 42, app_id: 1 }); const res = await request(makeApp()).get("/probe").set("Authorization", "Bearer good-key"); expect(res.status).toBe(200); expect(res.body.userId).toBe(42); expect(verifyKey).toHaveBeenCalledWith("good-key"); expect(query).not.toHaveBeenCalled(); // hashed path short-circuits the legacy fallback }); it("falls back to the legacy plaintext auth key when the hashed store misses", async () => { verifyKey.mockResolvedValue(null); query.mockResolvedValue({ rowCount: 1, rows: [{ user_id: 7 }] }); const res = await request(makeApp()).get("/probe").set("Authorization", "Bearer legacy-key"); expect(res.status).toBe(200); expect(res.body.userId).toBe(7); expect(query.mock.calls[0][1]).toEqual(["legacy-key"]); expect(release).toHaveBeenCalledTimes(1); }); it("rejects an unknown key with 401", async () => { verifyKey.mockResolvedValue(null); query.mockResolvedValue({ rowCount: 0, rows: [] }); const res = await request(makeApp()).get("/probe").set("Authorization", "Bearer bogus"); expect(res.status).toBe(401); }); it("rejects a request with no Authorization header with 401 (no lookups)", async () => { const res = await request(makeApp()).get("/probe"); expect(res.status).toBe(401); expect(verifyKey).not.toHaveBeenCalled(); expect(query).not.toHaveBeenCalled(); }); });