Files
STCourses/packages/feature-learner/test/auth.test.ts
T

63 lines
3.4 KiB
TypeScript

import { expect, test } from "bun:test";
import { assignInitialRole } from "../src/server/auth";
import { syncClaimGroups, groupsFromClaims } from "../src/server/auth/groups";
import { forEachDb } from "./helpers";
import { cookieFrom, makeApp } from "./app";
forEachDb("first sign-up becomes owner, later ones learners", async (db) => {
const t = await makeApp(db);
const ownerCookie = await t.signUp("[email protected]");
const me = await (await t.call("GET", "/api/me", { cookie: ownerCookie })).json();
expect(me.user.email).toBe("[email protected]");
expect(me.user.roles).toEqual(["owner"]);
const second = await t.signUp("[email protected]");
expect((await (await t.call("GET", "/api/me", { cookie: second })).json()).user.roles).toEqual(["learner"]);
expect((await (await t.call("GET", "/api/me")).json()).user).toBeNull();
}, 20_000);
forEachDb("sign in with password and sign out", async (db) => {
const t = await makeApp(db);
await t.signUp("[email protected]", "A", "pw-123456789");
const bad = await t.call("POST", "/api/auth/sign-in/email", { body: { email: "[email protected]", password: "nope-nope-nope" } });
expect(bad.status).toBe(401);
const ok = await t.call("POST", "/api/auth/sign-in/email", { body: { email: "[email protected]", password: "pw-123456789" } });
expect(ok.status).toBe(200);
const cookie = cookieFrom(ok);
expect((await (await t.call("GET", "/api/me", { cookie })).json()).user.name).toBe("A");
const out = await t.call("POST", "/api/auth/sign-out", { cookie, body: {} });
expect(out.status).toBe(200);
// the session row is gone: the old token no longer resolves once the cookie cache is dropped
const sessionOnly = cookie.split("; ").filter((c) => !c.includes("session_data")).join("; ");
expect((await (await t.call("GET", "/api/me", { cookie: sessionOnly })).json()).user).toBeNull();
}, 20_000);
forEachDb("owner claim is exclusive under concurrency", async (db) => {
const roles = await Promise.all(["u1", "u2", "u3", "u4"].map((u) => assignInitialRole(db, u)));
expect(roles.filter((r) => r === "owner").length).toBe(1);
});
forEachDb("SSO provider administration is owner-only", async (db) => {
const t = await makeApp(db);
await t.signUp("[email protected]");
const learner = await t.signUp("[email protected]");
expect((await t.call("POST", "/api/auth/sso/register", { cookie: learner, body: {} })).status).toBe(403);
expect((await t.call("POST", "/api/auth/sso/register", { body: {} })).status).toBe(403);
}, 20_000);
forEachDb("claim groups sync keeps manual memberships", async (db) => {
await db.sql`INSERT INTO groups (id, name) VALUES ('staff', 'Staff')`;
await db.sql`INSERT INTO group_members (group_id, user_id) VALUES ('staff', 'u1')`;
await syncClaimGroups(db, "u1", ["Staff", "DevOps Team"]);
const ids = async () => (await db.sql`SELECT group_id FROM group_members WHERE user_id = 'u1' ORDER BY group_id`).map((r: { group_id: string }) => r.group_id);
expect(await ids()).toEqual(["devops-team", "staff"]);
await syncClaimGroups(db, "u1", []);
expect(await ids()).toEqual(["staff"]); // manual one stays, claim one goes
});
test("groupsFromClaims reads arrays and strings", () => {
expect(groupsFromClaims({ groups: ["a", "b", "a"] })).toEqual(["a", "b"]);
expect(groupsFromClaims({ roles: "x, y" }, "roles")).toEqual(["x", "y"]);
expect(groupsFromClaims({ extraFields: { groups: ["z"] } })).toEqual(["z"]);
expect(groupsFromClaims(null)).toEqual([]);
});