feat: show planning availability and manage Kita access roles
This commit is contained in:
@@ -0,0 +1,599 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports -- Test real server code with isolated request and transport boundaries. */
|
||||
const { test, before, after, beforeEach } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
const ts = require("typescript");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const { randomUUID } = require("node:crypto");
|
||||
let prisma,
|
||||
current,
|
||||
f,
|
||||
change,
|
||||
changeAction,
|
||||
remove,
|
||||
support,
|
||||
updateFamily,
|
||||
alarms,
|
||||
alarmMail,
|
||||
staff,
|
||||
staffMail,
|
||||
assign;
|
||||
const emails = [];
|
||||
const tenants = [],
|
||||
looseUsers = [];
|
||||
function loader(overrides = {}) {
|
||||
const cache = new Map();
|
||||
function load(file) {
|
||||
file = path.resolve(file);
|
||||
if (cache.has(file)) return cache.get(file).exports;
|
||||
const mod = { exports: {} };
|
||||
cache.set(file, mod);
|
||||
const code = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
esModuleInterop: true,
|
||||
},
|
||||
fileName: file,
|
||||
}).outputText;
|
||||
const req = (spec) => {
|
||||
if (Object.hasOwn(overrides, spec)) return overrides[spec];
|
||||
if (spec === "server-only") return {};
|
||||
if (spec === "@/lib/prisma") return { prisma };
|
||||
if (spec === "next/headers")
|
||||
return { headers: async () => new Headers() };
|
||||
if (spec === "next/cache") return { revalidatePath() {} };
|
||||
if (spec === "next/navigation")
|
||||
return {
|
||||
redirect(url) {
|
||||
throw new Error(`REDIRECT:${url}`);
|
||||
},
|
||||
notFound() {
|
||||
throw new Error("NOT_FOUND");
|
||||
},
|
||||
};
|
||||
if (spec.startsWith("@/") || spec.startsWith(".")) {
|
||||
const target = spec.startsWith("@/")
|
||||
? path.resolve("src", spec.slice(2))
|
||||
: path.resolve(path.dirname(file), spec);
|
||||
for (const suffix of [".ts", ".tsx", "/index.ts"])
|
||||
if (fs.existsSync(target + suffix)) return load(target + suffix);
|
||||
}
|
||||
return require(spec);
|
||||
};
|
||||
vm.runInThisContext(`(function(require,module,exports){${code}\n})`, {
|
||||
filename: file,
|
||||
})(req, mod, mod.exports);
|
||||
return mod.exports;
|
||||
}
|
||||
return load;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
const raw = process.env.TEST_DATABASE_URL;
|
||||
assert.ok(
|
||||
raw,
|
||||
"TEST_DATABASE_URL muss ausdrücklich auf die separate lokale kita_roles_test zeigen.",
|
||||
);
|
||||
const url = new URL(raw);
|
||||
assert.ok(["127.0.0.1", "localhost"].includes(url.hostname));
|
||||
assert.equal(url.pathname, "/kita_roles_test");
|
||||
prisma = new PrismaClient({ datasourceUrl: raw });
|
||||
const load = loader({
|
||||
"@/lib/auth-utils": {
|
||||
requireKitaSession: async () => ({ user: current }),
|
||||
requireRole: async (roles) => {
|
||||
assert.ok(roles.includes(current.role));
|
||||
return { user: current };
|
||||
},
|
||||
},
|
||||
"@/lib/mail": {
|
||||
getAppEmailConfigError: () => null,
|
||||
sendAppEmail: async (input) => {
|
||||
emails.push(input);
|
||||
return { success: true };
|
||||
},
|
||||
},
|
||||
});
|
||||
change = load("src/lib/kita-role-management.ts").changeKitaRole;
|
||||
changeAction = load(
|
||||
"src/app/dashboard/zugriffsrechte/actions.ts",
|
||||
).changeKitaRoleAction;
|
||||
remove = load("src/app/dashboard/profil/actions.ts").deleteMyAccount;
|
||||
support = load("src/app/admin/actions.ts");
|
||||
updateFamily = load(
|
||||
"src/app/dashboard/families/actions.ts",
|
||||
).updateFamilyAction;
|
||||
alarms = load("src/lib/alarms/service.ts");
|
||||
alarmMail = load("src/lib/alarms/email.ts");
|
||||
staff = load("src/lib/staff/service.ts");
|
||||
staffMail = load("src/lib/staff/email.ts");
|
||||
assign = load(
|
||||
"src/app/dashboard/notdienst/plan/actions.ts",
|
||||
).updateAssignmentAction;
|
||||
process.env.AUTH_URL = "http://127.0.0.1:3008";
|
||||
});
|
||||
async function user(role, extra = {}, kitaId = f.kita.id) {
|
||||
return prisma.user.create({
|
||||
data: {
|
||||
kitaId,
|
||||
email: `${randomUUID()}@example.invalid`,
|
||||
firstName: "Test",
|
||||
lastName: role,
|
||||
passwordHash: "test-only",
|
||||
emailVerifiedAt: new Date(),
|
||||
privacyPolicyAcceptedAt: new Date(),
|
||||
role,
|
||||
...extra,
|
||||
},
|
||||
});
|
||||
}
|
||||
const actor = (u) => ({
|
||||
id: u.id,
|
||||
kitaId: u.kitaId,
|
||||
sessionVersion: u.sessionVersion,
|
||||
});
|
||||
const changeTo = (u, role, by = f.admin) =>
|
||||
change(actor(by), { userId: u.id, role, expectedRole: u.role });
|
||||
beforeEach(async () => {
|
||||
const kita = await prisma.kita.create({
|
||||
data: { name: "Rollen-Test", slug: `roles-${randomUUID()}` },
|
||||
});
|
||||
tenants.push(kita.id);
|
||||
f = { kita };
|
||||
f.admin = await user("ADMIN");
|
||||
f.family = await prisma.family.create({
|
||||
data: { kitaId: kita.id, name: "Beispiel" },
|
||||
});
|
||||
f.parent = await user("ELTERN", { familyId: f.family.id });
|
||||
f.child = await prisma.child.create({
|
||||
data: {
|
||||
kitaId: kita.id,
|
||||
familyId: f.family.id,
|
||||
firstName: "Kind",
|
||||
lastName: "Test",
|
||||
},
|
||||
});
|
||||
current = f.admin;
|
||||
emails.length = 0;
|
||||
});
|
||||
after(async () => {
|
||||
if (!prisma) return;
|
||||
await prisma.kita.deleteMany({ where: { id: { in: tenants } } });
|
||||
await prisma.user.deleteMany({ where: { id: { in: looseUsers } } });
|
||||
await prisma.auditLog.deleteMany({
|
||||
where: { targetKitaId: { in: tenants } },
|
||||
});
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
test("Eltern werden Koordination und Kita-Admin; Haushalt und Kind bleiben erhalten", async () => {
|
||||
assert.equal(
|
||||
(
|
||||
await changeAction({
|
||||
userId: f.parent.id,
|
||||
expectedRole: "ELTERN",
|
||||
role: "KOORDINATOR",
|
||||
})
|
||||
).success,
|
||||
true,
|
||||
);
|
||||
const coord = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: f.parent.id },
|
||||
});
|
||||
await changeTo(coord, "ADMIN");
|
||||
const updated = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: coord.id },
|
||||
});
|
||||
assert.equal(updated.familyId, f.family.id);
|
||||
assert.equal(updated.sessionVersion, coord.sessionVersion);
|
||||
assert.equal(
|
||||
await prisma.child.count({ where: { familyId: f.family.id } }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
test("Rollenänderung protokolliert Akteur, Kita und vorherige/neue Rolle genau einmal", async () => {
|
||||
await changeTo(f.parent, "ADMIN");
|
||||
const jobs = await prisma.auditLog.findMany({
|
||||
where: { targetKitaId: f.kita.id, action: "USER_ROLE_CHANGE" },
|
||||
});
|
||||
assert.equal(jobs.length, 1);
|
||||
assert.equal(jobs[0].actorUserId, f.admin.id);
|
||||
assert.deepEqual(jobs[0].metadata, {
|
||||
previousRole: "ELTERN",
|
||||
newRole: "ADMIN",
|
||||
});
|
||||
assert.equal(
|
||||
(await changeTo({ ...f.parent, role: "ADMIN" }, "ADMIN")).changed,
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
await prisma.auditLog.count({
|
||||
where: { targetKitaId: f.kita.id, action: "USER_ROLE_CHANGE" },
|
||||
}),
|
||||
1,
|
||||
);
|
||||
});
|
||||
test("Konto ohne Haushalt darf Koordination werden, aber nicht Eltern oder Fachkraft", async () => {
|
||||
const person = await user("KOORDINATOR");
|
||||
await assert.rejects(changeTo(person, "ELTERN"), /Haushalt/);
|
||||
await assert.rejects(changeTo(person, "ERZIEHER"), /Personalprofil/);
|
||||
assert.equal((await changeTo(person, "ADMIN")).changed, true);
|
||||
});
|
||||
test("Superadmin-Rolle kann weder vergeben noch verändert werden", async () => {
|
||||
await assert.rejects(changeTo(f.parent, "SUPERADMIN"), /Ungültige/);
|
||||
const root = await user("SUPERADMIN");
|
||||
await assert.rejects(
|
||||
change(actor(f.admin), {
|
||||
userId: root.id,
|
||||
expectedRole: "ADMIN",
|
||||
role: "KOORDINATOR",
|
||||
}),
|
||||
/nicht geändert/,
|
||||
);
|
||||
});
|
||||
test("Fremde Kita ist bei Zielkonto und Akteur ausgeschlossen", async () => {
|
||||
const foreign = await prisma.kita.create({
|
||||
data: { name: "Andere", slug: `roles-${randomUUID()}` },
|
||||
});
|
||||
tenants.push(foreign.id);
|
||||
const outsider = await user("ADMIN", {}, foreign.id);
|
||||
await assert.rejects(changeTo(outsider, "KOORDINATOR"), /nicht geändert/);
|
||||
await assert.rejects(
|
||||
change(
|
||||
{ ...actor(f.admin), kitaId: foreign.id },
|
||||
{ userId: outsider.id, expectedRole: "ADMIN", role: "KOORDINATOR" },
|
||||
),
|
||||
/nicht mehr ändern/,
|
||||
);
|
||||
});
|
||||
test("Eltern, gesperrte Admins und veraltete Sessions dürfen keine Rollen ändern", async () => {
|
||||
await assert.rejects(
|
||||
change(actor(f.parent), {
|
||||
userId: f.admin.id,
|
||||
expectedRole: "ADMIN",
|
||||
role: "KOORDINATOR",
|
||||
}),
|
||||
/nicht mehr ändern/,
|
||||
);
|
||||
await assert.rejects(
|
||||
change(
|
||||
{ ...actor(f.admin), sessionVersion: 9 },
|
||||
{ userId: f.parent.id, expectedRole: "ELTERN", role: "ADMIN" },
|
||||
),
|
||||
/nicht mehr ändern/,
|
||||
);
|
||||
await prisma.user.update({
|
||||
where: { id: f.admin.id },
|
||||
data: { disabledAt: new Date() },
|
||||
});
|
||||
await assert.rejects(changeTo(f.parent, "ADMIN"), /nicht mehr ändern/);
|
||||
});
|
||||
test("Veralteter Dialog überschreibt keine zwischenzeitliche Rollenänderung", async () => {
|
||||
await changeTo(f.parent, "KOORDINATOR");
|
||||
await assert.rejects(changeTo(f.parent, "ADMIN"), /inzwischen geändert/);
|
||||
assert.equal(
|
||||
(await prisma.user.findUnique({ where: { id: f.parent.id } })).role,
|
||||
"KOORDINATOR",
|
||||
);
|
||||
});
|
||||
test("Letzter Admin kann seine Rolle nicht abgeben", async () => {
|
||||
await assert.rejects(
|
||||
changeTo(f.admin, "KOORDINATOR"),
|
||||
/Mindestens ein aktiver/,
|
||||
);
|
||||
});
|
||||
for (const [label, extra] of [
|
||||
["offene Einladung", { emailVerifiedAt: null, passwordHash: "" }],
|
||||
["deaktiviert", { disabledAt: new Date() }],
|
||||
["gesperrt", { lockedUntil: new Date("2099-01-01") }],
|
||||
["ohne Zustimmung", { privacyPolicyAcceptedAt: null }],
|
||||
]) {
|
||||
test(`Ein weiterer Admin ${label} reicht nicht als Ersatz`, async () => {
|
||||
await user("ADMIN", extra);
|
||||
await assert.rejects(
|
||||
changeTo(f.admin, "KOORDINATOR"),
|
||||
/Mindestens ein aktiver/,
|
||||
);
|
||||
});
|
||||
}
|
||||
test("Mit zweitem aktivierten Admin ist eigene Herabstufung möglich und stale Admin-Rechte sind unwirksam", async () => {
|
||||
await user("ADMIN");
|
||||
assert.equal((await changeTo(f.admin, "KOORDINATOR")).ownRoleChanged, true);
|
||||
await assert.rejects(changeTo(f.parent, "ADMIN"), /nicht mehr ändern/);
|
||||
});
|
||||
test("Parallele Herabstufung zweier Admins lässt genau einen Admin bestehen", async () => {
|
||||
const second = await user("ADMIN");
|
||||
const results = await Promise.allSettled([
|
||||
changeTo(f.admin, "KOORDINATOR", f.admin),
|
||||
changeTo(second, "KOORDINATOR", second),
|
||||
]);
|
||||
assert.equal(results.filter((r) => r.status === "fulfilled").length, 1);
|
||||
assert.equal(
|
||||
await prisma.user.count({ where: { kitaId: f.kita.id, role: "ADMIN" } }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
test("Eigene Kontolöschung ist für letzten Admin gesperrt", async () => {
|
||||
assert.match((await remove()).error, /Mindestens ein aktiver/);
|
||||
assert.ok(await prisma.user.findUnique({ where: { id: f.admin.id } }));
|
||||
});
|
||||
test("Eigene Kontolöschung mit zweitem aktivem Admin funktioniert weiterhin", async () => {
|
||||
await user("ADMIN");
|
||||
assert.equal((await remove()).success, true);
|
||||
assert.equal(
|
||||
await prisma.user.findUnique({ where: { id: f.admin.id } }),
|
||||
null,
|
||||
);
|
||||
});
|
||||
test("Superadmin kann den letzten Kita-Admin weder löschen noch sperren", async () => {
|
||||
current = await user("SUPERADMIN", {}, null);
|
||||
looseUsers.push(current.id);
|
||||
current.authenticatedAt = Date.now();
|
||||
assert.match(
|
||||
(
|
||||
await support.deleteUser({
|
||||
userId: f.admin.id,
|
||||
confirmationEmail: f.admin.email,
|
||||
})
|
||||
).error,
|
||||
/Mindestens ein aktiver/,
|
||||
);
|
||||
assert.match(
|
||||
(await support.lockUser(f.admin.id)).error,
|
||||
/Mindestens ein aktiver/,
|
||||
);
|
||||
});
|
||||
test("Support darf zusätzlichen Admin sperren und löschen", async () => {
|
||||
const second = await user("ADMIN");
|
||||
current = await user("SUPERADMIN", {}, null);
|
||||
looseUsers.push(current.id);
|
||||
current.authenticatedAt = Date.now();
|
||||
assert.equal((await support.lockUser(second.id)).ok, true);
|
||||
assert.equal(
|
||||
(
|
||||
await support.deleteUser({
|
||||
userId: second.id,
|
||||
confirmationEmail: second.email,
|
||||
})
|
||||
).ok,
|
||||
true,
|
||||
);
|
||||
});
|
||||
test("Gleichzeitige Admin-Löschung und Herabstufung lässt einen Admin bestehen", async () => {
|
||||
const second = await user("ADMIN");
|
||||
const results = await Promise.allSettled([
|
||||
remove(),
|
||||
changeTo(second, "KOORDINATOR"),
|
||||
]);
|
||||
const successful =
|
||||
results.filter((r) => r.status === "fulfilled" && r.value.success).length +
|
||||
results.filter((r) => r.status === "fulfilled" && r.value.changed).length;
|
||||
assert.equal(successful, 1);
|
||||
assert.equal(
|
||||
await prisma.user.count({ where: { kitaId: f.kita.id, role: "ADMIN" } }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
test("E-Mail-Änderung darf letzten aktivierten Admin nicht deaktivieren", async () => {
|
||||
await prisma.user.update({
|
||||
where: { id: f.admin.id },
|
||||
data: { familyId: f.family.id },
|
||||
});
|
||||
const payload = {
|
||||
familyId: f.family.id,
|
||||
familyName: f.family.name,
|
||||
parents: [
|
||||
f.parent,
|
||||
{ ...f.admin, email: `${randomUUID()}@example.invalid` },
|
||||
],
|
||||
children: [f.child],
|
||||
};
|
||||
assert.match((await updateFamily(payload)).error, /Mindestens ein aktiver/);
|
||||
assert.equal(
|
||||
(await prisma.user.findUnique({ where: { id: f.admin.id } })).email,
|
||||
f.admin.email,
|
||||
);
|
||||
});
|
||||
|
||||
test("Fachkraft wird Admin; Personalprofil bleibt bearbeitbar und eigene Mailpräferenz erhalten", async () => {
|
||||
const person = await user("ERZIEHER");
|
||||
const educator = await prisma.educator.create({
|
||||
data: { kitaId: f.kita.id, firstName: "Team", lastName: "Test" },
|
||||
});
|
||||
const profile = await prisma.staffProfile.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
educatorId: educator.id,
|
||||
userId: person.id,
|
||||
employmentStart: new Date("2030-01-01"),
|
||||
trackingStart: new Date("2030-01-01"),
|
||||
},
|
||||
});
|
||||
await assert.rejects(changeTo(person, "KOORDINATOR"), /Personalprofil/);
|
||||
await changeTo(person, "ADMIN");
|
||||
await staff.executeStaffCommand(
|
||||
{ id: person.id, kitaId: f.kita.id, role: "ADMIN" },
|
||||
{ command: "emailPreference", data: { enabled: false } },
|
||||
);
|
||||
await staff.executeStaffCommand(
|
||||
{ id: f.admin.id, kitaId: f.kita.id, role: "ADMIN" },
|
||||
{
|
||||
command: "educatorUpdate",
|
||||
data: {
|
||||
educatorId: educator.id,
|
||||
firstName: "Team",
|
||||
lastName: "Geändert",
|
||||
active: true,
|
||||
email: person.email,
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(
|
||||
(await prisma.staffProfile.findUnique({ where: { id: profile.id } }))
|
||||
.userId,
|
||||
person.id,
|
||||
);
|
||||
assert.equal(
|
||||
(await prisma.user.findUnique({ where: { id: person.id } }))
|
||||
.staffDecisionEmails,
|
||||
false,
|
||||
);
|
||||
await changeTo({ ...person, role: "ADMIN" }, "ERZIEHER");
|
||||
});
|
||||
test("Offene Fachkraft-Einladung lässt sich auch nach Admin-Ernennung erneut senden", async () => {
|
||||
const person = await user("ERZIEHER", {
|
||||
emailVerifiedAt: null,
|
||||
passwordHash: "",
|
||||
});
|
||||
const educator = await prisma.educator.create({
|
||||
data: { kitaId: f.kita.id, firstName: "Team", lastName: "Test" },
|
||||
});
|
||||
await prisma.staffProfile.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
educatorId: educator.id,
|
||||
userId: person.id,
|
||||
employmentStart: new Date("2030-01-01"),
|
||||
trackingStart: new Date("2030-01-01"),
|
||||
},
|
||||
});
|
||||
await changeTo(person, "ADMIN");
|
||||
const queued = await staff.executeStaffCommand(
|
||||
{ id: f.admin.id, kitaId: f.kita.id, role: "ADMIN" },
|
||||
{ command: "educatorInvite", data: { educatorId: educator.id } },
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await staffMail.deliverStaffEmails(queued.emailIds, async () => ({
|
||||
success: true,
|
||||
}))
|
||||
).sent,
|
||||
1,
|
||||
);
|
||||
});
|
||||
for (const role of ["ADMIN", "KOORDINATOR"])
|
||||
test(`Haushaltsalarme erreichen und akzeptieren Eltern mit Rolle ${role}`, async () => {
|
||||
await changeTo(f.parent, role);
|
||||
const plan = await prisma.notdienstPlan.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
year: 2030,
|
||||
month: 11,
|
||||
status: "PUBLISHED",
|
||||
assignments: {
|
||||
create: {
|
||||
kitaId: f.kita.id,
|
||||
childId: f.child.id,
|
||||
date: new Date("2030-11-04"),
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { assignments: true },
|
||||
});
|
||||
const job = await alarms.executeAlarmCommand(
|
||||
{ id: f.admin.id, kitaId: f.kita.id, role: "ADMIN" },
|
||||
"create",
|
||||
{ assignmentId: plan.assignments[0].id, wholeDay: true },
|
||||
);
|
||||
const request = await prisma.notdienstAlarmEmail.findFirstOrThrow({
|
||||
where: { alarmId: job.alarmId, userId: f.parent.id, kind: "REQUEST" },
|
||||
});
|
||||
const token = request.path.split("/").pop();
|
||||
assert.ok(await alarms.getPublicAlarm(token));
|
||||
assert.equal(
|
||||
(
|
||||
await alarmMail.deliverAlarmEmails([request.id], async () => ({
|
||||
success: true,
|
||||
}))
|
||||
).sent,
|
||||
1,
|
||||
);
|
||||
await alarms.respondToAlarm(token, true);
|
||||
assert.equal(
|
||||
(await prisma.notdienstAlarm.findUnique({ where: { id: job.alarmId } }))
|
||||
.status,
|
||||
"CONFIRMED",
|
||||
);
|
||||
});
|
||||
test("Bereits ausgestellter Haushalts-Alarmlink bleibt nach Ernennung gültig", async () => {
|
||||
const plan = await prisma.notdienstPlan.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
year: 2030,
|
||||
month: 11,
|
||||
status: "PUBLISHED",
|
||||
assignments: {
|
||||
create: {
|
||||
kitaId: f.kita.id,
|
||||
childId: f.child.id,
|
||||
date: new Date("2030-11-04"),
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { assignments: true },
|
||||
});
|
||||
const job = await alarms.executeAlarmCommand(
|
||||
{ id: f.admin.id, kitaId: f.kita.id, role: "ADMIN" },
|
||||
"create",
|
||||
{ assignmentId: plan.assignments[0].id, wholeDay: true },
|
||||
);
|
||||
const request = await prisma.notdienstAlarmEmail.findFirstOrThrow({
|
||||
where: { alarmId: job.alarmId, userId: f.parent.id, kind: "REQUEST" },
|
||||
});
|
||||
await changeTo(f.parent, "ADMIN");
|
||||
assert.ok(await alarms.getPublicAlarm(request.path.split("/").pop()));
|
||||
});
|
||||
|
||||
test("Letzter Admin im Ein-Personen-Haushalt bleibt auch vor kaskadierender Familienlöschung geschützt", async () => {
|
||||
await prisma.user.update({
|
||||
where: { id: f.parent.id },
|
||||
data: { role: "ADMIN" },
|
||||
});
|
||||
await prisma.user.update({
|
||||
where: { id: f.admin.id },
|
||||
data: { role: "KOORDINATOR" },
|
||||
});
|
||||
current = { ...f.parent, role: "ADMIN" };
|
||||
assert.match((await remove()).error, /Mindestens ein aktiver/);
|
||||
assert.ok(await prisma.family.findUnique({ where: { id: f.family.id } }));
|
||||
assert.ok(await prisma.child.findUnique({ where: { id: f.child.id } }));
|
||||
});
|
||||
|
||||
test("Veröffentlichte Umteilung informiert auch den Admin im betroffenen Elternhaushalt", async () => {
|
||||
await changeTo(f.parent, "ADMIN");
|
||||
const family = await prisma.family.create({
|
||||
data: { kitaId: f.kita.id, name: "Ersatz" },
|
||||
});
|
||||
const parent = await user("ELTERN", { familyId: family.id });
|
||||
const child = await prisma.child.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
familyId: family.id,
|
||||
firstName: "Ersatz",
|
||||
lastName: "Test",
|
||||
},
|
||||
});
|
||||
const plan = await prisma.notdienstPlan.create({
|
||||
data: {
|
||||
kitaId: f.kita.id,
|
||||
year: 2030,
|
||||
month: 11,
|
||||
status: "PUBLISHED",
|
||||
assignments: {
|
||||
create: {
|
||||
kitaId: f.kita.id,
|
||||
childId: f.child.id,
|
||||
date: new Date("2030-11-04"),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = await assign(plan.id, "2030-11-04", child.id);
|
||||
assert.equal(result.success, true);
|
||||
assert.ok(emails.some((email) => [email.to].flat().includes(f.parent.email)));
|
||||
assert.ok(emails.some((email) => [email.to].flat().includes(parent.email)));
|
||||
});
|
||||
Reference in New Issue
Block a user