Establishes the test suite. The repo previously had zero tests and no test framework installed. Tooling: - Vitest 2 added as devDependency. Chosen for ESM-native + TypeScript out-of-the-box, no Babel/ts-node, and fast cold starts. - vitest.config.ts sets environment=node and wires the '@/' path alias so tests can import app modules by the same path the app uses. - package.json scripts: 'test' (vitest run, CI-friendly) and 'test:watch'; also adds the long-missing 'typecheck' wrapper for 'tsc --noEmit'. - tsconfig.json now excludes *.test.ts from the app's build graph so the production bundle doesn't pull in test files (the editor still type-checks them via vitest). Tests: - admin-session.test.ts (8 cases): sign/verify round-trip for ADMIN and SUPER_ADMIN, tampered payload rejection (privilege-escalation attempt — should be rejected because the HMAC no longer matches), tampered signature rejection, missing-separator token, invalid base64/JSON payload, and cookie option flags (httpOnly, sameSite, path, secure under NODE_ENV=test vs production). - rate-limit.test.ts (4 cases): basic token bucket within window, independent key tracking, refill after window elapses (fake timers), and remaining-counter accounting. 12 tests, all green.
82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
import { describe, it, expect, beforeAll } from "vitest";
|
|
import {
|
|
createAdminSession,
|
|
verifyAdminSession,
|
|
COOKIE_NAME_ADMIN,
|
|
cookieOptions,
|
|
} from "../admin-session";
|
|
|
|
const SECRET = "a".repeat(64);
|
|
|
|
beforeAll(() => {
|
|
process.env.ADMIN_SESSION_SECRET = SECRET;
|
|
process.env.NEXT_PUBLIC_APP_URL = "https://testbed.mk";
|
|
});
|
|
|
|
describe("admin-session.sign/verify", () => {
|
|
it("round-trips a valid session", async () => {
|
|
const token = await createAdminSession({ username: "alice", role: "ADMIN" });
|
|
const parsed = await verifyAdminSession(token);
|
|
expect(parsed).toEqual({ username: "alice", role: "ADMIN" });
|
|
});
|
|
|
|
it("round-trips SUPER_ADMIN role", async () => {
|
|
const token = await createAdminSession({ username: "super", role: "SUPER_ADMIN" });
|
|
const parsed = await verifyAdminSession(token);
|
|
expect(parsed).toEqual({ username: "super", role: "SUPER_ADMIN" });
|
|
});
|
|
|
|
it("rejects a tampered payload (signature no longer matches)", async () => {
|
|
const token = await createAdminSession({ username: "alice", role: "ADMIN" });
|
|
const [payload, sig] = token.split(".");
|
|
const tamperedPayload = btoa(
|
|
JSON.stringify({ username: "alice", role: "SUPER_ADMIN" })
|
|
);
|
|
const tampered = `${tamperedPayload}.${sig}`;
|
|
const parsed = await verifyAdminSession(tampered);
|
|
expect(parsed).toBeNull();
|
|
expect(payload).not.toEqual(tamperedPayload);
|
|
});
|
|
|
|
it("rejects a tampered signature", async () => {
|
|
const token = await createAdminSession({ username: "alice", role: "ADMIN" });
|
|
const [payload] = token.split(".");
|
|
const forgedSig = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
|
const forged = `${payload}.${forgedSig}`;
|
|
const parsed = await verifyAdminSession(forged);
|
|
expect(parsed).toBeNull();
|
|
});
|
|
|
|
it("rejects a malformed token without separator", async () => {
|
|
const parsed = await verifyAdminSession("just-a-string-no-separator");
|
|
expect(parsed).toBeNull();
|
|
});
|
|
|
|
it("rejects a token whose payload is not valid base64 JSON", async () => {
|
|
const fakePayload = btoa("not-json-at-all");
|
|
const fakeToken = `${fakePayload}.aaaaaaaa`;
|
|
const parsed = await verifyAdminSession(fakeToken);
|
|
expect(parsed).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("admin-session.cookieOptions", () => {
|
|
it("emits the documented cookie name and secure flags", () => {
|
|
const opts = cookieOptions("token-value");
|
|
expect(opts.name).toBe(COOKIE_NAME_ADMIN);
|
|
expect(opts.value).toBe("token-value");
|
|
expect(opts.httpOnly).toBe(true);
|
|
expect(opts.sameSite).toBe("lax");
|
|
expect(opts.path).toBe("/");
|
|
});
|
|
|
|
it("marks the cookie secure only in production", () => {
|
|
const prev = process.env.NODE_ENV;
|
|
process.env.NODE_ENV = "production";
|
|
expect(cookieOptions("x").secure).toBe(true);
|
|
process.env.NODE_ENV = "test";
|
|
expect(cookieOptions("x").secure).toBe(false);
|
|
process.env.NODE_ENV = prev;
|
|
});
|
|
});
|