test: add auth gating tests for /api/requests
Mocks @/auth (vi.mock) instead of exercising the real auth(): that
helper reads cookies via next/headers under the hood, which only
works inside Next.js own request-handling machinery, not when these
tests import and call GET()/POST() directly. beforeEach mocks a
valid session by default so the existing business-logic tests are
unaffected, and two new tests mock a null session to assert both
GET and POST return 401 with { error: Unauthorized } and that POST
does not create a row.
middleware.ts itself has no automated test - it runs on the Edge
Runtime and is not exercised by vitest calling route handlers
directly. Verified manually instead via next build + next start:
curl confirmed GET / redirects to /login (307), GET /login stays
200 with no redirect loop, GET/POST /api/requests return 401 JSON
unauthenticated, and GET /api/auth/session (needed for the login
flow) is not blocked.
This commit is contained in:
+66
-1
@@ -1,6 +1,34 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Mock } from "vitest";
|
||||
import type { Session } from "next-auth";
|
||||
|
||||
// The route handlers call auth() (src/auth.ts) directly to gate access (see
|
||||
// src/app/api/requests/route.ts). That auth() reads cookies via
|
||||
// next/headers under the hood, which only works inside Next.js's own
|
||||
// request-handling machinery — not when these tests import and call
|
||||
// GET()/POST() directly, bypassing Next entirely (the same reason
|
||||
// middleware.ts itself isn't tested here; see CLAUDE.md's task notes).
|
||||
// Mocking @/auth sidesteps next/headers altogether and lets each test
|
||||
// control the session directly: a valid session by default (so the
|
||||
// existing business-logic tests below are unaffected), and no session for
|
||||
// the dedicated "returns 401" tests.
|
||||
vi.mock("@/auth", () => ({ auth: vi.fn() }));
|
||||
|
||||
import { GET, POST } from "@/app/api/requests/route";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
// auth's real type is next-auth's overloaded call signature (RSC helper,
|
||||
// middleware wrapper, API-route form, ...) — vi.mocked() picks the wrong
|
||||
// overload to infer mock*Value's parameter type from, so cast to the one
|
||||
// shape actually used here: the no-arg session-helper call the route
|
||||
// handlers make.
|
||||
const mockedAuth = auth as unknown as Mock<() => Promise<Session | null>>;
|
||||
|
||||
const testSession: Session = {
|
||||
user: { id: "test-user-id" },
|
||||
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
||||
};
|
||||
|
||||
function postRequest(body: unknown): Request {
|
||||
return new Request("http://localhost/api/requests", {
|
||||
@@ -14,6 +42,10 @@ beforeEach(async () => {
|
||||
// Each test starts from an empty table so listing/order assertions are
|
||||
// deterministic.
|
||||
await prisma.request.deleteMany();
|
||||
// Default to a logged-in session — the tests below exercise request
|
||||
// business logic, not auth gating (that's covered by the "auth gating"
|
||||
// describe block further down).
|
||||
mockedAuth.mockResolvedValue(testSession);
|
||||
});
|
||||
|
||||
describe("POST /api/requests", () => {
|
||||
@@ -91,3 +123,36 @@ describe("GET /api/requests", () => {
|
||||
expect(body).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth gating on /api/requests", () => {
|
||||
// Route-level coverage for the "logged in or not" gate added alongside
|
||||
// middleware.ts (src/middleware.ts) — see that file's comment for why
|
||||
// page routes are gated via middleware but API routes gate themselves.
|
||||
// No role/permission logic here, per the flat permission model (any
|
||||
// logged-in user can do anything) — this only checks session presence.
|
||||
it("POST /api/requests returns 401 without a session", async () => {
|
||||
mockedAuth.mockResolvedValue(null);
|
||||
|
||||
const res = await POST(
|
||||
postRequest({ employee_id: "emp-1", type: "asset", reason: "New laptop for onboarding" })
|
||||
);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
const body = await res.json();
|
||||
expect(body).toEqual({ error: "Unauthorized" });
|
||||
|
||||
// And it must not have created anything.
|
||||
const remaining = await prisma.request.findMany();
|
||||
expect(remaining).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("GET /api/requests returns 401 without a session", async () => {
|
||||
mockedAuth.mockResolvedValue(null);
|
||||
|
||||
const res = await GET();
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
const body = await res.json();
|
||||
expect(body).toEqual({ error: "Unauthorized" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user