Rollen Chunk 6: Team-Tab mit Matrix-Kalender und Genehmigungen
This commit is contained in:
1 parent
f35afca3f5
commit
8fe7700256
1 file changed
+228
@@ -0,0 +1,228 @@
|
||||
import "../../../test-helpers";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { getAuth } from "@/lib/auth";
|
||||
import { getDb, initializeDatabase } from "@/lib/db";
|
||||
import { POST } from "./route";
|
||||
import { saveVacationRequest, saveYearSettings } from "@/lib/repository";
|
||||
|
||||
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: authPost } = await import("../../../auth/[...all]/route");
|
||||
const response = await authPost(
|
||||
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 adminPost(
|
||||
path: string,
|
||||
cookie: string,
|
||||
body: unknown,
|
||||
): Promise<Response> {
|
||||
const { POST: authPost } = await import("../../../auth/[...all]/route");
|
||||
return authPost(
|
||||
new Request(`http://localhost${path}`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", cookie },
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
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 setup(): Promise<{ leiter: string; benutzer: string }> {
|
||||
await signUp("Admina", "admina@example.org");
|
||||
const admin = await cookieFor("admina@example.org");
|
||||
await adminPost("/api/auth/admin/create-user", admin, {
|
||||
name: "Leitung",
|
||||
email: "leitung@example.org",
|
||||
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 leiterId = listed.users?.find(
|
||||
(user) => user.email === "leitung@example.org",
|
||||
)?.id;
|
||||
if (!leiterId) {
|
||||
throw new Error("Leiter-ID erwartet");
|
||||
}
|
||||
await adminPost("/api/auth/admin/set-role", admin, {
|
||||
userId: leiterId,
|
||||
role: "teamleiter",
|
||||
});
|
||||
await adminPost("/api/auth/admin/create-user", admin, {
|
||||
name: "Benutzer",
|
||||
email: "benutzer@example.org",
|
||||
password: "geheim-geheim!",
|
||||
role: "benutzer",
|
||||
});
|
||||
return {
|
||||
leiter: await cookieFor("leitung@example.org"),
|
||||
benutzer: await cookieFor("benutzer@example.org"),
|
||||
};
|
||||
}
|
||||
|
||||
function postJsonWithCookie(url: string, cookie: string, body: unknown): Request {
|
||||
return new Request(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", cookie },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
describe("POST /api/vacation/[id]/status", () => {
|
||||
test("Teamleiter bestätigt eine Anfrage", async () => {
|
||||
const { leiter, benutzer } = await setup();
|
||||
const db = getDb();
|
||||
initializeDatabase(db);
|
||||
const benutzerId = db
|
||||
.prepare('SELECT id FROM "user" WHERE email = ?')
|
||||
.get("benutzer@example.org") as { id: string };
|
||||
const saved = 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 POST(
|
||||
postJsonWithCookie(
|
||||
`http://localhost/api/vacation/${saved.id}/status`,
|
||||
leiter,
|
||||
{ action: "genehmigen" },
|
||||
),
|
||||
{ params: Promise.resolve({ id: String(saved.id) }) },
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
expect(((await response.json()) as { status: string }).status).toBe(
|
||||
"bestätigt",
|
||||
);
|
||||
void benutzer;
|
||||
});
|
||||
|
||||
test("Benutzer erhalten 403, Unbekannte Anfrage 404, falsche Aktion 400", async () => {
|
||||
const { leiter, benutzer } = await setup();
|
||||
const db = getDb();
|
||||
initializeDatabase(db);
|
||||
const benutzerId = db
|
||||
.prepare('SELECT id FROM "user" WHERE email = ?')
|
||||
.get("benutzer@example.org") as { id: string };
|
||||
const saved = saveVacationRequest(db, benutzerId.id, {
|
||||
startDate: "2026-07-06",
|
||||
endDate: "2026-07-06",
|
||||
dayFraction: "full",
|
||||
status: "beantragt",
|
||||
days: [{ date: "2026-07-06", units: 2 }],
|
||||
});
|
||||
const denied = await POST(
|
||||
postJsonWithCookie(
|
||||
`http://localhost/api/vacation/${saved.id}/status`,
|
||||
benutzer,
|
||||
{ action: "genehmigen" },
|
||||
),
|
||||
{ params: Promise.resolve({ id: String(saved.id) }) },
|
||||
);
|
||||
expect(denied.status).toBe(403);
|
||||
const wrongAction = await POST(
|
||||
postJsonWithCookie(
|
||||
`http://localhost/api/vacation/${saved.id}/status`,
|
||||
leiter,
|
||||
{ action: "verschieben" },
|
||||
),
|
||||
{ params: Promise.resolve({ id: String(saved.id) }) },
|
||||
);
|
||||
expect(wrongAction.status).toBe(400);
|
||||
const missing = await POST(
|
||||
postJsonWithCookie("http://localhost/api/vacation/999/status", leiter, {
|
||||
action: "genehmigen",
|
||||
}),
|
||||
{ params: Promise.resolve({ id: "999" }) },
|
||||
);
|
||||
expect(missing.status).toBe(404);
|
||||
});
|
||||
|
||||
test("ohne Bestätigung würde der Anspruch überzogen: 422", async () => {
|
||||
const { leiter, benutzer } = await setup();
|
||||
const db = getDb();
|
||||
initializeDatabase(db);
|
||||
const benutzerId = db
|
||||
.prepare('SELECT id FROM "user" WHERE email = ?')
|
||||
.get("benutzer@example.org") as { id: string };
|
||||
saveVacationRequest(db, benutzerId.id, {
|
||||
startDate: "2026-01-05",
|
||||
endDate: "2026-01-05",
|
||||
dayFraction: "full",
|
||||
status: "beantragt",
|
||||
days: [{ date: "2026-01-05", units: 2 }],
|
||||
});
|
||||
const second = saveVacationRequest(db, benutzerId.id, {
|
||||
startDate: "2026-01-06",
|
||||
endDate: "2026-01-06",
|
||||
dayFraction: "full",
|
||||
status: "beantragt",
|
||||
days: [{ date: "2026-01-06", units: 2 }],
|
||||
});
|
||||
saveVacationRequest(db, benutzerId.id, {
|
||||
startDate: "2026-01-07",
|
||||
endDate: "2026-01-07",
|
||||
dayFraction: "full",
|
||||
status: "beantragt",
|
||||
days: [{ date: "2026-01-07", units: 2 }],
|
||||
});
|
||||
// Kontingent absenken:
|
||||
saveYearSettings(db, benutzerId.id, 2026, {
|
||||
entitlementUnits: 3,
|
||||
carryoverUnits: 0,
|
||||
});
|
||||
const response = await POST(
|
||||
postJsonWithCookie(
|
||||
`http://localhost/api/vacation/${second.id}/status`,
|
||||
leiter,
|
||||
{ action: "genehmigen" },
|
||||
),
|
||||
{ params: Promise.resolve({ id: String(second.id) }) },
|
||||
);
|
||||
expect(response.status).toBe(422);
|
||||
expect((await response.json()).error.code).toBe("vacationExceedsBalance");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user