Rollen Chunk 6: Team-Tab mit Matrix-Kalender und Genehmigungen
This commit is contained in:
1 parent
9346945e80
commit
73ade9d4ec
1 file changed
+161
@@ -0,0 +1,161 @@
|
||||
import "../test-helpers";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { getAuth } from "@/lib/auth";
|
||||
import { getDb, initializeDatabase } from "@/lib/db";
|
||||
import { GET } from "./route";
|
||||
import { saveWeeklyTargets, saveVacationRequest } from "@/lib/repository";
|
||||
import { putJson } from "../test-helpers";
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.AUTH_MODE = "password";
|
||||
process.env.BETTER_AUTH_SECRET = "test-secret-mindestens-32-zeichen-lang!";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.AUTH_MODE;
|
||||
delete process.env.BETTER_AUTH_SECRET;
|
||||
});
|
||||
|
||||
async function signUp(name: string, email: string): Promise<void> {
|
||||
const { POST } = await import("../auth/[...all]/route");
|
||||
const response = await POST(
|
||||
new Request("http://localhost/api/auth/sign-up/email", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ name, email, password: "geheim-geheim!" }),
|
||||
}),
|
||||
);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Registrierung: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function cookieFor(email: string): Promise<string> {
|
||||
const auth = getAuth();
|
||||
if (!auth) {
|
||||
throw new Error("Auth erwartet");
|
||||
}
|
||||
const response = await auth.api.signInEmail({
|
||||
body: { email, password: "geheim-geheim!" },
|
||||
asResponse: true,
|
||||
});
|
||||
const setCookie = response.headers
|
||||
.getSetCookie()
|
||||
.find((entry) => entry.startsWith("better-auth.session_token="));
|
||||
if (!setCookie) {
|
||||
throw new Error("Sitzungscookie erwartet");
|
||||
}
|
||||
return setCookie.split(";")[0];
|
||||
}
|
||||
|
||||
async function adminGet(
|
||||
path: string,
|
||||
cookie: string,
|
||||
): Promise<Response> {
|
||||
const { GET: authGet } = await import("../auth/[...all]/route");
|
||||
return authGet(new Request(`http://localhost${path}`, { headers: { cookie } }));
|
||||
}
|
||||
|
||||
async function adminPost(
|
||||
path: string,
|
||||
cookie: string,
|
||||
body: unknown,
|
||||
): Promise<Response> {
|
||||
const { POST } = await import("../auth/[...all]/route");
|
||||
return POST(
|
||||
new Request(`http://localhost${path}`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", cookie },
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function setup(): Promise<{ admin: string; team: string; benutzer: string }> {
|
||||
await signUp("Admina", "admina@example.org");
|
||||
const admin = await cookieFor("admina@example.org");
|
||||
for (const [name, email] of [
|
||||
["Leitung", "leitung@example.org"],
|
||||
["Benutzer", "benutzer@example.org"],
|
||||
] as const) {
|
||||
await adminPost("/api/auth/admin/create-user", admin, {
|
||||
name,
|
||||
email,
|
||||
password: "geheim-geheim!",
|
||||
role: "benutzer",
|
||||
});
|
||||
const listed = (await (
|
||||
await adminGet("/api/auth/admin/list-users?limit=10", admin)
|
||||
).json()) as { users?: { id: string; email: string }[] };
|
||||
const id = listed.users?.find((user) => user.email === email)?.id;
|
||||
if (!id) {
|
||||
throw new Error("Benutzer-ID erwartet");
|
||||
}
|
||||
await adminPost("/api/auth/admin/set-role", admin, {
|
||||
userId: id,
|
||||
role: email === "leitung@example.org" ? "teamleiter" : "benutzer",
|
||||
});
|
||||
}
|
||||
return {
|
||||
admin,
|
||||
team: await cookieFor("leitung@example.org"),
|
||||
benutzer: await cookieFor("benutzer@example.org"),
|
||||
};
|
||||
}
|
||||
|
||||
function authed(path: string, cookie: string): Request {
|
||||
return new Request(path, { headers: { cookie } });
|
||||
}
|
||||
|
||||
describe("GET /api/team", () => {
|
||||
test("403 für normale Benutzer, 401 ohne Sitzung", async () => {
|
||||
const { team, benutzer } = await setup();
|
||||
const denied = await GET(authed("http://localhost/api/team?year=2026", benutzer));
|
||||
expect(denied.status).toBe(403);
|
||||
expect((await denied.json()).error.code).toBe("forbidden");
|
||||
const anonymous = await GET(new Request("http://localhost/api/team?year=2026"));
|
||||
expect(anonymous.status).toBe(401);
|
||||
void team;
|
||||
});
|
||||
|
||||
test("liefert Team mit Salden und Buchungen", async () => {
|
||||
const { team, benutzer } = await setup();
|
||||
const db = getDb();
|
||||
initializeDatabase(db);
|
||||
const { listAllUsers, sumVacationUnitsByYear } = await import("@/lib/repository");
|
||||
void listAllUsers;
|
||||
void sumVacationUnitsByYear;
|
||||
const benutzerId = db
|
||||
.prepare('SELECT id FROM "user" WHERE email = ?')
|
||||
.get("benutzer@example.org") as { id: string };
|
||||
saveWeeklyTargets(db, benutzerId.id, { 0: 0, 1: 480, 2: 480, 3: 480, 4: 480, 5: 480, 6: 0 });
|
||||
saveVacationRequest(db, benutzerId.id, {
|
||||
startDate: "2026-07-06",
|
||||
endDate: "2026-07-06",
|
||||
dayFraction: "full",
|
||||
status: "beantragt",
|
||||
days: [{ date: "2026-07-06", units: 2 }],
|
||||
});
|
||||
const response = await GET(authed("http://localhost/api/team?year=2026", team));
|
||||
expect(response.status).toBe(200);
|
||||
const body = (await response.json()) as {
|
||||
team: Array<{
|
||||
name: string;
|
||||
role: string;
|
||||
entitlementUnits: number;
|
||||
pendingUnits: number;
|
||||
requests: Array<{ status: string; startDate: string }>;
|
||||
sickDays: unknown[];
|
||||
weekdayTargets: Record<string, number> | null;
|
||||
}>;
|
||||
};
|
||||
const benutzerRow = body.team.find((row) => row.role === "benutzer");
|
||||
if (!benutzerRow) {
|
||||
throw new Error("Benutzerzeile erwartet");
|
||||
}
|
||||
expect(benutzerRow.name).toBe("Benutzer");
|
||||
expect(benutzerRow.pendingUnits).toBe(2);
|
||||
expect(benutzerRow.requests[0].status).toBe("beantragt");
|
||||
expect(benutzerRow.weekdayTargets?.[1]).toBe(480);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user