first commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import {
|
||||
createContext,
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
logIn,
|
||||
logOut,
|
||||
getUser,
|
||||
sessionAuth,
|
||||
requireAuth,
|
||||
} from "../src/index.ts";
|
||||
|
||||
function ctx(method = "GET", path = "/", accept?: string) {
|
||||
const headers: Record<string, string> = {};
|
||||
if (accept) headers.accept = accept;
|
||||
const url = new URL(`http://x${path}`);
|
||||
const req = new Request(url, { method, headers });
|
||||
return createContext(req, url);
|
||||
}
|
||||
|
||||
test("hashPassword / verifyPassword round-trip", async () => {
|
||||
const hash = await hashPassword("correct horse battery staple");
|
||||
expect(hash).toBeTruthy();
|
||||
expect(hash).not.toBe("correct horse battery staple");
|
||||
expect(await verifyPassword("correct horse battery staple", hash)).toBe(true);
|
||||
expect(await verifyPassword("wrong", hash)).toBe(false);
|
||||
});
|
||||
|
||||
test("verifyPassword tolerates empty/garbage hashes", async () => {
|
||||
expect(await verifyPassword("x", "")).toBe(false);
|
||||
expect(await verifyPassword("x", "not-a-real-hash")).toBe(false);
|
||||
});
|
||||
|
||||
test("logIn stores the user; getUser reads it; logOut clears it", () => {
|
||||
const c = ctx();
|
||||
expect(getUser(c)).toBeNull();
|
||||
logIn(c, { id: 1, email: "a@b.com" });
|
||||
expect(getUser<{ id: number }>(c)?.id).toBe(1);
|
||||
expect(c.session.get<{ id: number; email: string }>("user")).toEqual({ id: 1, email: "a@b.com" });
|
||||
logOut(c);
|
||||
expect(getUser(c)).toBeNull();
|
||||
expect(c.user).toBeNull();
|
||||
});
|
||||
|
||||
test("logIn regenerates the session id (fixation defense) but keeps data", () => {
|
||||
const c = ctx();
|
||||
c.session.set("cart", [1, 2]);
|
||||
const before = c.session.id();
|
||||
logIn(c, { id: 1, email: "a@b.com" });
|
||||
const after = c.session.id();
|
||||
expect(after).not.toBe(before); // fresh id issued on login
|
||||
expect(after.length).toBeGreaterThanOrEqual(32);
|
||||
expect(c.session.get<number[]>("cart")).toEqual([1, 2]); // data preserved
|
||||
expect(getUser<{ id: number }>(c)?.id).toBe(1);
|
||||
});
|
||||
|
||||
test("sessionAuth hydrates ctx.user from the session", async () => {
|
||||
const c = ctx();
|
||||
c.session.set("user", { id: 7 });
|
||||
let seen: unknown = "unset";
|
||||
await sessionAuth()(c, () => {
|
||||
seen = c.user;
|
||||
return new Response("ok");
|
||||
});
|
||||
expect(seen).toEqual({ id: 7 });
|
||||
});
|
||||
|
||||
test("requireAuth: passes through when authenticated", async () => {
|
||||
const c = ctx();
|
||||
logIn(c, { id: 1 });
|
||||
const res = await requireAuth()(c, () => new Response("secret"));
|
||||
expect(await res.text()).toBe("secret");
|
||||
});
|
||||
|
||||
test("requireAuth: 401 JSON for API paths when anonymous", async () => {
|
||||
const c = ctx("GET", "/api/me");
|
||||
const res = await requireAuth()(c, () => new Response("secret"));
|
||||
expect(res.status).toBe(401);
|
||||
expect(await res.json()).toEqual({ ok: false, error: "Unauthorized" });
|
||||
});
|
||||
|
||||
test("requireAuth: 302 redirect for page navigations when anonymous", async () => {
|
||||
const c = ctx("GET", "/dashboard?tab=1", "text/html");
|
||||
const res = await requireAuth()(c, () => new Response("secret"));
|
||||
expect(res.status).toBe(302);
|
||||
expect(res.headers.get("location")).toBe("/login?next=%2Fdashboard%3Ftab%3D1");
|
||||
});
|
||||
|
||||
test("requireAuth: custom loginPath", async () => {
|
||||
const c = ctx("GET", "/dashboard", "text/html");
|
||||
const res = await requireAuth({ loginPath: "/signin" })(c, () => new Response("x"));
|
||||
expect(res.headers.get("location")).toBe("/signin?next=%2Fdashboard");
|
||||
});
|
||||
Reference in New Issue
Block a user