Reads the session cookie from next/headers, verifies via auth-cookie, loads the operators row, returns the shape every existing call site expects (.id, .defaultTimezone, etc) plus the new .role and .username. getSeededOperator stays as a thin compat shim that delegates to getCurrentUser, so the ~12 tests that mock @/lib/operator keep working without churn.
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const cookiesGetMock = vi.fn();
|
|
const findUserMock = vi.fn();
|
|
|
|
vi.mock("next/headers", () => ({
|
|
cookies: async () => ({ get: cookiesGetMock }),
|
|
}));
|
|
vi.mock("./db", () => ({
|
|
db: {
|
|
query: {
|
|
operators: {
|
|
findFirst: (...a: unknown[]) => findUserMock(...a),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const SECRET = "test-secret";
|
|
beforeEach(() => {
|
|
process.env.AUTH_SECRET = SECRET;
|
|
process.env.OPERATOR_TOKEN_VERSION = "1";
|
|
cookiesGetMock.mockReset();
|
|
findUserMock.mockReset();
|
|
});
|
|
|
|
import { signSession } from "./auth-cookie";
|
|
import { getCurrentUser, requireUser, requireAdmin } from "./auth";
|
|
|
|
const NOW_S = Math.floor(Date.now() / 1000);
|
|
const ADMIN = {
|
|
id: "11111111-1111-1111-1111-111111111111",
|
|
username: "admin",
|
|
role: "admin" as const,
|
|
displayName: "Admin",
|
|
defaultTimezone: "UTC",
|
|
passwordHash: null,
|
|
};
|
|
const USER = { ...ADMIN, id: "22222222-2222-2222-2222-222222222222", username: "alice", role: "user" as const };
|
|
|
|
async function makeCookie(role: "admin" | "user"): Promise<string> {
|
|
return signSession(
|
|
{
|
|
userId: role === "admin" ? ADMIN.id : USER.id,
|
|
role,
|
|
iat: NOW_S,
|
|
exp: NOW_S + 3600,
|
|
v: 1,
|
|
},
|
|
SECRET,
|
|
);
|
|
}
|
|
|
|
describe("auth helpers", () => {
|
|
it("getCurrentUser returns null when no cookie is set", async () => {
|
|
cookiesGetMock.mockReturnValue(undefined);
|
|
const u = await getCurrentUser();
|
|
expect(u).toBeNull();
|
|
});
|
|
|
|
it("getCurrentUser returns the user row for a valid admin cookie", async () => {
|
|
const cookie = await makeCookie("admin");
|
|
cookiesGetMock.mockReturnValue({ value: cookie });
|
|
findUserMock.mockResolvedValue(ADMIN);
|
|
const u = await getCurrentUser();
|
|
expect(u?.id).toBe(ADMIN.id);
|
|
expect(u?.role).toBe("admin");
|
|
});
|
|
|
|
it("requireUser throws when there is no session", async () => {
|
|
cookiesGetMock.mockReturnValue(undefined);
|
|
await expect(requireUser()).rejects.toThrow();
|
|
});
|
|
|
|
it("requireAdmin throws when role is 'user'", async () => {
|
|
const cookie = await makeCookie("user");
|
|
cookiesGetMock.mockReturnValue({ value: cookie });
|
|
findUserMock.mockResolvedValue(USER);
|
|
await expect(requireAdmin()).rejects.toThrow();
|
|
});
|
|
|
|
it("requireAdmin returns the user when role is 'admin'", async () => {
|
|
const cookie = await makeCookie("admin");
|
|
cookiesGetMock.mockReturnValue({ value: cookie });
|
|
findUserMock.mockResolvedValue(ADMIN);
|
|
const u = await requireAdmin();
|
|
expect(u.role).toBe("admin");
|
|
});
|
|
});
|