feat: create and invite educators in a single form
This commit is contained in:
@@ -709,3 +709,272 @@ test("Entzogene Rollen, fehlender Consent und gesperrte Accounts können keine P
|
||||
});
|
||||
await assert.rejects(f.run(f.staff, "time", time()), /keinen Zugriff/);
|
||||
});
|
||||
|
||||
function onboarding(email = `${randomUUID()}@staff-test.invalid`) {
|
||||
return {
|
||||
firstName: " Neue ",
|
||||
lastName: " Fachkraft ",
|
||||
active: true,
|
||||
email,
|
||||
employmentStart: "2024-08-01",
|
||||
trackingStart: "2026-10-09",
|
||||
};
|
||||
}
|
||||
|
||||
test("Gemeinsame Anlage speichert Fachkraft, Profil, Account und wiederholbaren Mailauftrag", async () => {
|
||||
const f = await fixture();
|
||||
const input = onboarding();
|
||||
const result = await executeStaffCommand(f.admin, {
|
||||
command: "educatorCreate",
|
||||
data: input,
|
||||
});
|
||||
const educator = await prisma.educator.findUniqueOrThrow({
|
||||
where: { id: result.educatorId },
|
||||
include: { staffProfile: { include: { user: true } } },
|
||||
});
|
||||
assert.equal(educator.firstName, "Neue");
|
||||
assert.equal(educator.kitaId, f.kita.id);
|
||||
assert.equal(educator.staffProfile?.user?.email, input.email);
|
||||
assert.equal(educator.staffProfile?.user?.emailVerifiedAt, null);
|
||||
assert.equal(educator.staffProfile?.user?.role, "ERZIEHER");
|
||||
assert.equal(
|
||||
educator.staffProfile?.employmentStart.toISOString().slice(0, 10),
|
||||
"2024-08-01",
|
||||
);
|
||||
assert.equal(
|
||||
educator.staffProfile?.trackingStart.toISOString().slice(0, 10),
|
||||
"2026-10-09",
|
||||
);
|
||||
assert.equal(educator.staffProfile?.openingMinutes, 0);
|
||||
assert.ok(result.invite);
|
||||
assert.ok(
|
||||
await prisma.verificationToken.findUnique({
|
||||
where: { token: result.invite.token },
|
||||
}),
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await deliverStaffEmails(result.emailIds!, async () => ({
|
||||
success: false,
|
||||
error: "Testausfall",
|
||||
}))
|
||||
).failed,
|
||||
1,
|
||||
);
|
||||
assert.ok(await prisma.educator.findUnique({ where: { id: educator.id } }));
|
||||
const retry = await executeStaffCommand(f.admin, {
|
||||
command: "emailRetry",
|
||||
data: {},
|
||||
});
|
||||
assert.deepEqual(retry.emailIds, result.emailIds);
|
||||
assert.equal(
|
||||
(
|
||||
await deliverStaffEmails(retry.emailIds!, async () => ({
|
||||
success: true,
|
||||
id: "mock",
|
||||
}))
|
||||
).sent,
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test("Pflichtfelder und Erfassungsdatum verhindern unvollständige oder falsche Anlagen", async () => {
|
||||
const f = await fixture();
|
||||
const before = await prisma.educator.count({ where: { kitaId: f.kita.id } });
|
||||
for (const input of [
|
||||
onboarding(""),
|
||||
{ ...onboarding(), firstName: " " },
|
||||
{ ...onboarding(), trackingStart: "2024-07-31" },
|
||||
{ ...onboarding(), employmentStart: "2026-02-30" },
|
||||
]) {
|
||||
await assert.rejects(
|
||||
executeStaffCommand(f.admin, { command: "educatorCreate", data: input }),
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
await prisma.educator.count({ where: { kitaId: f.kita.id } }),
|
||||
before,
|
||||
);
|
||||
});
|
||||
|
||||
test("Doppelte oder fremde Accounts rollen die gesamte Anlage zurück", async () => {
|
||||
const f = await fixture(),
|
||||
foreign = await fixture();
|
||||
const parent = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: f.parent.id },
|
||||
});
|
||||
const before = await prisma.educator.count({ where: { kitaId: f.kita.id } });
|
||||
for (const email of [
|
||||
f.staffUser.email,
|
||||
foreign.staffUser.email,
|
||||
parent.email,
|
||||
]) {
|
||||
await assert.rejects(
|
||||
executeStaffCommand(f.admin, {
|
||||
command: "educatorCreate",
|
||||
data: onboarding(email),
|
||||
}),
|
||||
/bereits/,
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
await prisma.educator.count({ where: { kitaId: f.kita.id } }),
|
||||
before,
|
||||
);
|
||||
});
|
||||
|
||||
test("Freier aktivierter Fachkraft-Account derselben Kita wird verknüpft", async () => {
|
||||
const f = await fixture();
|
||||
const user = await prisma.user.update({
|
||||
where: { id: f.other.id },
|
||||
data: { emailVerifiedAt: new Date() },
|
||||
});
|
||||
const result = await executeStaffCommand(f.admin, {
|
||||
command: "educatorCreate",
|
||||
data: onboarding(user.email.toUpperCase()),
|
||||
});
|
||||
const profile = await prisma.staffProfile.findUniqueOrThrow({
|
||||
where: { educatorId: result.educatorId },
|
||||
});
|
||||
assert.equal(profile.userId, user.id);
|
||||
assert.equal(result.invite, undefined);
|
||||
assert.equal(result.emailIds, undefined);
|
||||
});
|
||||
|
||||
test("Bestehende Fachkraft ohne Profil erhält Zugang im Bearbeitungsablauf", async () => {
|
||||
const f = await fixture();
|
||||
const educator = await prisma.educator.create({
|
||||
data: { firstName: "Alt", lastName: "Ohne Profil", kitaId: f.kita.id },
|
||||
});
|
||||
const input = onboarding();
|
||||
await assert.rejects(
|
||||
executeStaffCommand(f.admin, {
|
||||
command: "educatorUpdate",
|
||||
data: { ...input, educatorId: educator.id, email: f.staffUser.email },
|
||||
}),
|
||||
/bereits/,
|
||||
);
|
||||
assert.equal(
|
||||
(await prisma.educator.findUniqueOrThrow({ where: { id: educator.id } }))
|
||||
.firstName,
|
||||
"Alt",
|
||||
);
|
||||
const result = await executeStaffCommand(f.admin, {
|
||||
command: "educatorUpdate",
|
||||
data: { ...input, educatorId: educator.id },
|
||||
});
|
||||
assert.equal(result.educatorId, educator.id);
|
||||
assert.ok(result.invite);
|
||||
assert.equal(
|
||||
await prisma.staffProfile.count({ where: { educatorId: educator.id } }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test("Bearbeiten schützt verknüpfte Identität, Zeiten und Konten", async () => {
|
||||
const f = await fixture();
|
||||
await assert.rejects(
|
||||
executeStaffCommand(f.admin, {
|
||||
command: "educatorUpdate",
|
||||
data: {
|
||||
...onboarding(),
|
||||
educatorId: f.educator.id,
|
||||
},
|
||||
}),
|
||||
/nicht geändert/,
|
||||
);
|
||||
await executeStaffCommand(f.admin, {
|
||||
command: "educatorUpdate",
|
||||
data: {
|
||||
...onboarding(f.staffUser.email),
|
||||
firstName: "Korrigiert",
|
||||
educatorId: f.educator.id,
|
||||
employmentStart: "2025-01-01",
|
||||
trackingStart: "2025-01-01",
|
||||
},
|
||||
});
|
||||
const profile = await getStaffProfile(f.admin, f.profile.id);
|
||||
assert.equal(profile?.openingMinutes, 600);
|
||||
assert.equal(
|
||||
profile?.employmentStart.toISOString().slice(0, 10),
|
||||
"2026-01-01",
|
||||
);
|
||||
assert.equal(profile?.workPatterns.length, 1);
|
||||
assert.equal(profile?.leaveAccounts.length, 1);
|
||||
assert.equal(profile?.user?.firstName, "Korrigiert");
|
||||
});
|
||||
|
||||
test("Einladung aus Teamliste erneuert nur eigene ausstehende Aktivierung", async () => {
|
||||
const f = await fixture(),
|
||||
foreign = await fixture();
|
||||
const result = await executeStaffCommand(f.admin, {
|
||||
command: "educatorCreate",
|
||||
data: onboarding(),
|
||||
});
|
||||
for (const actor of [f.staff, f.parent, foreign.admin]) {
|
||||
await assert.rejects(
|
||||
executeStaffCommand(actor, {
|
||||
command: "educatorInvite",
|
||||
data: { educatorId: result.educatorId },
|
||||
}),
|
||||
);
|
||||
}
|
||||
const renewed = await executeStaffCommand(f.admin, {
|
||||
command: "educatorInvite",
|
||||
data: { educatorId: result.educatorId },
|
||||
});
|
||||
assert.ok(renewed.invite);
|
||||
assert.notEqual(renewed.invite.token, result.invite?.token);
|
||||
assert.equal(
|
||||
await prisma.verificationToken.count({
|
||||
where: { token: result.invite?.token },
|
||||
}),
|
||||
0,
|
||||
);
|
||||
const profile = await prisma.staffProfile.findUniqueOrThrow({
|
||||
where: { educatorId: result.educatorId },
|
||||
});
|
||||
await prisma.user.update({
|
||||
where: { id: profile.userId! },
|
||||
data: { emailVerifiedAt: new Date() },
|
||||
});
|
||||
await assert.rejects(
|
||||
executeStaffCommand(f.admin, {
|
||||
command: "educatorInvite",
|
||||
data: { educatorId: result.educatorId },
|
||||
}),
|
||||
/bereits einen Account/,
|
||||
);
|
||||
});
|
||||
|
||||
test("Nur aktuelle Kita-Admins dürfen Fachkräfte anlegen und bearbeiten", async () => {
|
||||
const f = await fixture();
|
||||
const before = await prisma.educator.count({ where: { kitaId: f.kita.id } });
|
||||
for (const actor of [
|
||||
f.staff,
|
||||
f.parent,
|
||||
{ ...f.staff, role: UserRole.ADMIN },
|
||||
]) {
|
||||
await assert.rejects(
|
||||
executeStaffCommand(actor, {
|
||||
command: "educatorCreate",
|
||||
data: onboarding(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
const foreign = await fixture();
|
||||
await assert.rejects(
|
||||
executeStaffCommand(foreign.admin, {
|
||||
command: "educatorUpdate",
|
||||
data: {
|
||||
...onboarding(f.staffUser.email),
|
||||
educatorId: f.educator.id,
|
||||
},
|
||||
}),
|
||||
/nicht gefunden/,
|
||||
);
|
||||
assert.equal(
|
||||
await prisma.educator.count({ where: { kitaId: f.kita.id } }),
|
||||
before,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user