Files
Laufzettel/tests/geraete.test.ts
T
florian.wilde f85bc87324
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(stammdaten): Mitarbeiter, Abteilungen und Geraete (#25)
2026-09-25 10:52:44 +00:00

560 lines
24 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it } from "vitest";
import { Betriebsmodus, GeraeteTyp, Rolle } from "@/generated/prisma/enums";
import { istTagCode } from "@/lib/tagcode";
import {
GERAETE_IMPORT_HOECHSTZEILEN,
GeraeteFehler,
geraeteImportieren,
geraetAnlegen,
geraetArchivieren,
geraetBearbeiten,
geraetLoeschen,
} from "@/server/services/geraete";
import { besitzerKandidaten, geraetHolen, geraeteListe } from "@/server/queries/geraete";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Geräte, Tag-Codes und der CSV-Import (#011).
*
* Festgehalten werden die Zusagen aus dem Auftrag: ein registriertes Gerät hat
* einen Namen, ein `tagCode` ändert sich nie und kommt nie zurück, an einem
* Gerät mit Läufen wird nichts gelöscht, und eine kaputte CSV-Zeile bricht den
* Import nicht ab. Dazu die Mandantensicht des Lesemodells — im Review von #009
* war genau dort ein Suchfilter, der die Kundenbedingung überschrieben hat.
*/
async function grundbestand(modus: Betriebsmodus = Betriebsmodus.INTERN) {
const eigene = await testPrisma.kunde.create({
data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
await testPrisma.einstellung.create({ data: { id: "global", modus } });
const benutzer = await testPrisma.benutzer.create({
data: { email: "admin@firma.example", name: "Administrator", rolle: Rolle.ADMIN },
});
return { eigene, admin: { id: benutzer.id, rolle: Rolle.ADMIN } };
}
/** Ein Lauf an einem Gerät – mehr braucht es dafür nicht als eine veröffentlichte Version. */
async function laufAnlegen(kundeId: string, geraetId: string) {
const vorlage = await testPrisma.vorlage.create({ data: { name: "PC-Neuinstallation" } });
const version = await testPrisma.vorlagenVersion.create({
data: {
vorlageId: vorlage.id,
version: 1,
status: "VEROEFFENTLICHT",
veroeffentlichtAm: new Date(),
},
});
return testPrisma.lauf.create({
data: { vorlagenVersionId: version.id, titel: "Neuinstallation", kundeId, geraetId },
});
}
describe.runIf(datenbankVerfuegbar)("Geräte", () => {
beforeEach(async () => {
await datenbankLeeren();
delete process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
});
describe("Anlegen", () => {
it("erzeugt einen Tag-Code und schreibt ihn ins Vergabebuch", async () => {
const { admin, eigene } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-VERTRIEB-07", typ: GeraeteTyp.PC }, admin);
expect(istTagCode(geraet.tagCode)).toBe(true);
expect(geraet.kundeId).toBe(eigene.id);
expect(geraet.registriertAm).not.toBeNull();
// Beides in einer Transaktion: ein Code ohne Eintrag wäre nach dem Löschen
// des Geräts wieder frei.
await expect(
testPrisma.tagCodeVergabe.findUnique({ where: { tagCode: geraet.tagCode } }),
).resolves.not.toBeNull();
});
it("lehnt ein registriertes Gerät ohne Hostname und ohne Bezeichnung ab", async () => {
const { admin } = await grundbestand();
await expect(geraetAnlegen({ seriennummer: "SN-1" }, admin)).rejects.toThrow(GeraeteFehler);
await expect(geraetAnlegen({ hostname: " ", bezeichnung: "" }, admin)).rejects.toThrow(
/Hostname oder eine Bezeichnung/,
);
// Und es ist auch kein Tag-Code verbraucht worden.
expect(await testPrisma.tagCodeVergabe.count()).toBe(0);
expect(await testPrisma.geraet.count()).toBe(0);
});
it("erlaubt einen vorgedruckten Tag ohne jeden Namen", async () => {
const { admin } = await grundbestand();
const tag = await geraetAnlegen({ registrieren: false }, admin);
expect(tag.registriertAm).toBeNull();
expect(tag.hostname).toBeNull();
expect(tag.bezeichnung).toBeNull();
});
it("nimmt einen Besitzer nur, wenn er zum selben Kunden gehört", async () => {
const { admin, eigene } = await grundbestand();
const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } });
const eigenePerson = await testPrisma.mitarbeiter.create({
data: { kundeId: eigene.id, vorname: "Ada", nachname: "Lovelace", anzeigename: "Ada Lovelace" },
});
const fremdePerson = await testPrisma.mitarbeiter.create({
data: { kundeId: fremder.id, vorname: "Max", nachname: "Fremd", anzeigename: "Max Fremd" },
});
const geraet = await geraetAnlegen(
{ hostname: "NB-ADA", typ: GeraeteTyp.NOTEBOOK, mitarbeiterId: eigenePerson.id },
admin,
);
expect(geraet.mitarbeiterId).toBe(eigenePerson.id);
await expect(
geraetAnlegen({ hostname: "NB-MAX", mitarbeiterId: fremdePerson.id }, admin),
).rejects.toThrow(/anderen Kunden/);
});
it("protokolliert das Anlegen", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ bezeichnung: "Serverschrank Keller" }, admin);
const eintraege = await testPrisma.protokoll.findMany({
where: { entitaet: "Geraet", entitaetId: geraet.id },
});
expect(eintraege).toHaveLength(1);
expect(eintraege[0]?.aktion).toBe("Gerät angelegt");
expect(eintraege[0]?.nachher).toMatchObject({ tagCode: geraet.tagCode });
});
});
describe("Bearbeiten", () => {
it("weist jeden Änderungsversuch am Tag-Code ab", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1" }, admin);
await expect(
geraetBearbeiten(geraet.id, { tagCode: "LZ-ZZZZZZ", hostname: "PC-2" }, admin),
).rejects.toThrow(/bleibt, wie er ist/);
// Nichts ist passiert – auch nicht der Hostname, der im selben Aufruf stand.
const frisch = await testPrisma.geraet.findUniqueOrThrow({ where: { id: geraet.id } });
expect(frisch.tagCode).toBe(geraet.tagCode);
expect(frisch.hostname).toBe("PC-1");
});
it("lässt den unveränderten Tag-Code mitkommen, ohne sich zu beschweren", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1" }, admin);
const nachher = await geraetBearbeiten(geraet.id, { tagCode: geraet.tagCode, hostname: "PC-2" }, admin);
expect(nachher.hostname).toBe("PC-2");
});
it("lässt den Hostname nicht leeren, wenn dann kein Name übrig bleibt", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1" }, admin);
await expect(geraetBearbeiten(geraet.id, { hostname: "" }, admin)).rejects.toThrow(
/Hostname oder eine Bezeichnung/,
);
// Mit einer Bezeichnung als Ersatz geht es.
const nachher = await geraetBearbeiten(
geraet.id,
{ hostname: "", bezeichnung: "Kiste im Regal" },
admin,
);
expect(nachher.hostname).toBeNull();
expect(nachher.bezeichnung).toBe("Kiste im Regal");
});
it("protokolliert nur, was sich geändert hat", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1", seriennummer: "SN-1" }, admin);
await geraetBearbeiten(geraet.id, { hostname: "PC-2", seriennummer: "SN-1" }, admin);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaet: "Geraet", entitaetId: geraet.id, aktion: { contains: "geändert" } },
});
expect(eintrag.aktion).toBe("Hostname geändert");
expect(eintrag.vorher).toMatchObject({ Hostname: "PC-1" });
expect(eintrag.nachher).toMatchObject({ Hostname: "PC-2" });
expect(eintrag.nachher).not.toHaveProperty("Seriennummer");
});
it("schreibt gar nichts, wenn sich nichts geändert hat", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1" }, admin);
await geraetBearbeiten(geraet.id, { hostname: "PC-1" }, admin);
expect(await testPrisma.protokoll.count({ where: { aktion: { contains: "geändert" } } })).toBe(0);
});
it("registriert einen vorgedruckten Tag, sobald er einen Namen bekommt", async () => {
const { admin } = await grundbestand();
const tag = await geraetAnlegen({ registrieren: false }, admin);
await expect(geraetBearbeiten(tag.id, { registrieren: true }, admin)).rejects.toThrow(
/Hostname oder eine Bezeichnung/,
);
const nachher = await geraetBearbeiten(tag.id, { registrieren: true, hostname: "PC-NEU" }, admin);
expect(nachher.registriertAm).not.toBeNull();
expect(nachher.tagCode).toBe(tag.tagCode);
});
});
describe("Archivieren und Löschen", () => {
it("lässt ein Gerät mit Läufen nicht löschen, nur archivieren", async () => {
const { admin, eigene } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-1" }, admin);
await laufAnlegen(eigene.id, geraet.id);
await expect(geraetLoeschen(geraet.id, admin)).rejects.toThrow(/nur archivieren/);
expect(await testPrisma.geraet.count()).toBe(1);
await geraetArchivieren(geraet.id, true, admin);
const frisch = await testPrisma.geraet.findUniqueOrThrow({ where: { id: geraet.id } });
expect(frisch.archiviert).toBe(true);
// Und zurück.
await geraetArchivieren(geraet.id, false, admin);
await expect(testPrisma.geraet.findUniqueOrThrow({ where: { id: geraet.id } })).resolves.toMatchObject({
archiviert: false,
});
});
it("gibt einen Tag-Code auch nach dem Löschen nicht zurück", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-WEG" }, admin);
await geraetLoeschen(geraet.id, admin);
expect(await testPrisma.geraet.count()).toBe(0);
// Das Vergabebuch behält ihn: ein übrig gebliebener Aufkleber darf nie auf
// ein zweites Objekt zeigen.
await expect(
testPrisma.tagCodeVergabe.findUnique({ where: { tagCode: geraet.tagCode } }),
).resolves.not.toBeNull();
});
it("protokolliert das Löschen, und der Eintrag überlebt das Gerät", async () => {
const { admin } = await grundbestand();
const geraet = await geraetAnlegen({ hostname: "PC-WEG" }, admin);
await geraetLoeschen(geraet.id, admin);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaetId: geraet.id, aktion: "Gerät gelöscht" },
});
expect(eintrag.vorher).toMatchObject({ tagCode: geraet.tagCode, hostname: "PC-WEG" });
});
});
describe("CSV-Import", () => {
it("verliert keine Zeile an einem kaputten Anführungszeichen", async () => {
/*
* Der Blocker aus dem Review: ein Anführungszeichen, das in einer Zelle
* beginnt und nicht schliesst, verschluckte alle Folgezeilen bis zum
* naechsten `"`. Der Import meldete dafuer **Erfolg** — drei von drei
* Zeilen angelegt —, und in einer Bezeichnung stand der Text der
* verschluckten Zeilen. Jetzt endet der Schaden an seiner Zeile.
*/
const { admin } = await grundbestand();
const text = [
"hostname,label,serialNumber",
"PC-1,,SN-1",
'PC-2,"Regal,SN-2',
"PC-3,,SN-3",
'PC-4,"Kiste",SN-4',
"PC-5,,SN-5",
].join("\n");
const ergebnis = await geraeteImportieren(text, admin);
expect(ergebnis.angelegt).toBe(4);
expect(ergebnis.abgewiesen).toHaveLength(1);
expect(ergebnis.abgewiesen[0]?.zeile).toBe(3);
expect(ergebnis.abgewiesen[0]?.grund).toMatch(/Anführungszeichen/);
const namen = (await testPrisma.geraet.findMany({ select: { hostname: true } }))
.map((zeile) => zeile.hostname)
.sort();
expect(namen).toEqual(["PC-1", "PC-3", "PC-4", "PC-5"]);
});
it("weist eine doppelte Zeile ab, statt den Bestand zu verdoppeln", async () => {
// Dieselbe Datei zweimal eingelesen verdoppelte sonst alles – und weil ein
// tagCode nie zurueckkommt, waeren die Aufkleber dafuer schon gedruckt.
const { admin } = await grundbestand();
const text = ["hostname", "PC-DOPPELT", "PC-DOPPELT", "PC-EINMAL"].join("\n");
const erster = await geraeteImportieren(text, admin);
expect(erster.angelegt).toBe(2);
expect(erster.abgewiesen[0]?.grund).toMatch(/schon weiter oben/);
const zweiter = await geraeteImportieren(text, admin);
expect(zweiter.angelegt).toBe(0);
expect(zweiter.abgewiesen).toHaveLength(3);
expect(zweiter.abgewiesen[0]?.grund).toMatch(/gibt es schon/);
expect(await testPrisma.geraet.count()).toBe(2);
});
const KOPF = "hostname,label,serialNumber,inventoryNumber,type,model";
it("legt die guten Zeilen an und meldet die kaputten einzeln", async () => {
const { admin } = await grundbestand();
const text = [
KOPF,
"PC-BUERO-01,,SN-1,INV-1,PC,OptiPlex",
",Serverschrank Keller,,,SONSTIGES,",
"PC-KAPUTT,,SN-3,,Tablet,", // unbekannter Typ
",,SN-4,,PC,", // weder Hostname noch Bezeichnung
"NB-05,,SN-5,,notebook,Latitude", // Schreibweise unerheblich
].join("\r\n");
const ergebnis = await geraeteImportieren(text, admin);
expect(ergebnis.gesamt).toBe(5);
expect(ergebnis.angelegt).toBe(3);
// Zeilennummern wie in der Datei: Kopfzeile ist 1.
expect(ergebnis.abgewiesen.map((a) => a.zeile)).toEqual([4, 5]);
expect(ergebnis.abgewiesen[0]?.grund).toMatch(/kein bekannter Gerätetyp/);
expect(ergebnis.abgewiesen[1]?.grund).toMatch(/Weder ein Hostname noch eine Bezeichnung/);
const geraete = await testPrisma.geraet.findMany({ orderBy: { tagCode: "asc" } });
expect(geraete.map((g) => g.hostname ?? g.bezeichnung).sort()).toEqual([
"NB-05",
"PC-BUERO-01",
"Serverschrank Keller",
]);
expect(geraete.find((g) => g.hostname === "NB-05")?.typ).toBe(GeraeteTyp.NOTEBOOK);
});
it("erzeugt je Zeile einen eigenen Tag-Code und trägt jeden ein", async () => {
const { admin } = await grundbestand();
const text = [KOPF, "PC-1,,,,PC,", "PC-2,,,,PC,", "PC-3,,,,PC,"].join("\n");
await geraeteImportieren(text, admin);
const codes = (await testPrisma.geraet.findMany({ select: { tagCode: true } })).map((g) => g.tagCode);
expect(codes).toHaveLength(3);
expect(new Set(codes).size).toBe(3);
expect(codes.every(istTagCode)).toBe(true);
expect(await testPrisma.tagCodeVergabe.count()).toBe(3);
});
it("versteht deutsches Excel: Semikolon, BOM und deutsche Spaltennamen", async () => {
const { admin } = await grundbestand();
const text =
"Hostname;Bezeichnung;Seriennummer;Inventarnummer;Typ;Modell\r\n" +
'PC-EXCEL;;"SN;mit;Semikolon";INV-9;Drucker;"Kyocera ""P3260"""\r\n';
const ergebnis = await geraeteImportieren(text, admin);
expect(ergebnis.angelegt).toBe(1);
const geraet = await testPrisma.geraet.findFirstOrThrow();
expect(geraet.seriennummer).toBe("SN;mit;Semikolon");
expect(geraet.modell).toBe('Kyocera "P3260"');
expect(geraet.typ).toBe(GeraeteTyp.DRUCKER);
});
it("setzt ohne Typspalte den Standard, ohne etwas zu erfinden", async () => {
const { admin } = await grundbestand();
const ergebnis = await geraeteImportieren("hostname\nPC-OHNE-TYP\n", admin);
expect(ergebnis.angelegt).toBe(1);
await expect(testPrisma.geraet.findFirstOrThrow()).resolves.toMatchObject({
typ: GeraeteTyp.SONSTIGES,
});
});
it("weist eine Datei ohne Namensspalte als Ganzes ab", async () => {
const { admin } = await grundbestand();
await expect(geraeteImportieren("serialNumber,type\nSN-1,PC\n", admin)).rejects.toThrow(/hostname/);
expect(await testPrisma.geraet.count()).toBe(0);
});
it("bricht bei zu vielen Zeilen ab, bevor eine einzige geschrieben ist", async () => {
const { admin } = await grundbestand();
const zeilen = Array.from({ length: GERAETE_IMPORT_HOECHSTZEILEN + 1 }, (_, i) => `PC-${i},,,,PC,`);
await expect(geraeteImportieren([KOPF, ...zeilen].join("\n"), admin)).rejects.toThrow(
new RegExp(`mehr als ${GERAETE_IMPORT_HOECHSTZEILEN} Zeilen`),
);
expect(await testPrisma.geraet.count()).toBe(0);
});
it("protokolliert jede eingelesene Zeile mit ihrer Nummer", async () => {
const { admin } = await grundbestand();
await geraeteImportieren([KOPF, "PC-1,,,,PC,"].join("\n"), admin);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { aktion: "Gerät aus CSV angelegt" },
});
expect(eintrag.nachher).toMatchObject({ hostname: "PC-1", zeile: 2 });
});
});
describe("Lesemodell und Filter", () => {
it("findet über Hostname, Bezeichnung, Seriennummer und Tag-Code", async () => {
const { admin } = await grundbestand();
const pc = await geraetAnlegen(
{ hostname: "PC-BUERO-01", seriennummer: "SN-4711", typ: GeraeteTyp.PC },
admin,
);
await geraetAnlegen({ bezeichnung: "Serverschrank Keller", typ: GeraeteTyp.SERVER }, admin);
const namen = async (suche: string) =>
(await geraeteListe(admin, { suche })).map((zeile) => zeile.anzeige);
expect(await namen("buero")).toEqual(["PC-BUERO-01"]);
expect(await namen("keller")).toEqual(["Serverschrank Keller"]);
expect(await namen("4711")).toEqual(["PC-BUERO-01"]);
// Die sechs Zeichen ohne das Präfix genügen – abgetippt vom Aufkleber.
expect(await namen(pc.tagCode.slice(3))).toEqual(["PC-BUERO-01"]);
});
it("filtert nach Typ und zeigt Archivierte nur auf Wunsch", async () => {
const { admin } = await grundbestand();
await geraetAnlegen({ hostname: "PC-1", typ: GeraeteTyp.PC }, admin);
const drucker = await geraetAnlegen({ bezeichnung: "Drucker Flur", typ: GeraeteTyp.DRUCKER }, admin);
await geraetArchivieren(drucker.id, true, admin);
expect((await geraeteListe(admin)).map((z) => z.anzeige)).toEqual(["PC-1"]);
expect((await geraeteListe(admin, { auchArchivierte: true })).map((z) => z.anzeige)).toEqual([
"PC-1",
"Drucker Flur",
]);
expect(
(await geraeteListe(admin, { typ: GeraeteTyp.DRUCKER, auchArchivierte: true })).map((z) => z.anzeige),
).toEqual(["Drucker Flur"]);
expect(await geraeteListe(admin, { typ: GeraeteTyp.FIREWALL })).toEqual([]);
});
it("zeigt Besitzer und Anzahl der Läufe", async () => {
const { admin, eigene } = await grundbestand();
const person = await testPrisma.mitarbeiter.create({
data: { kundeId: eigene.id, vorname: "Ada", nachname: "Lovelace", anzeigename: "Ada Lovelace" },
});
const geraet = await geraetAnlegen({ hostname: "NB-ADA", mitarbeiterId: person.id }, admin);
await laufAnlegen(eigene.id, geraet.id);
const zeile = await geraetHolen(geraet.id, admin);
expect(zeile?.besitzerName).toBe("Ada Lovelace");
expect(zeile?.besitzerAktiv).toBe(true);
expect(zeile?.anzahlLaeufe).toBe(1);
});
});
/**
* Die Mandantensicht. Der wichtigste Test dieser Datei: ein Suchbegriff darf
* die Kundenbedingung nicht aushebeln. Vor #009 tat er das, weil zwei `OR` im
* selben Objektliteral standen und das zweite gewann.
*/
describe("Mandantensicht", () => {
async function zweiKunden() {
const { eigene } = await grundbestand(Betriebsmodus.DIENSTLEISTUNG);
process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "lz-kunde-";
const meiner = await testPrisma.kunde.create({ data: { name: "Meiner", kuerzel: "MEIN" } });
const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } });
const techniker = await testPrisma.benutzer.create({
data: { email: "t@firma.example", name: "Techniker", rolle: Rolle.TECHNIKER },
});
await testPrisma.benutzerKunde.create({
data: { benutzerId: techniker.id, kundeId: meiner.id },
});
await testPrisma.geraet.create({
data: {
kundeId: meiner.id,
tagCode: "LZ-AAAAAA",
hostname: "GEHEIM-MEIN",
registriertAm: new Date(),
},
});
await testPrisma.geraet.create({
data: {
kundeId: fremder.id,
tagCode: "LZ-BBBBBB",
hostname: "GEHEIM-FREMD",
registriertAm: new Date(),
},
});
return { eigene, meiner, fremder, techniker: { id: techniker.id, rolle: Rolle.TECHNIKER } };
}
it("zeigt einem gebundenen Techniker nur die Geräte seiner Kunden", async () => {
const { techniker } = await zweiKunden();
expect((await geraeteListe(techniker)).map((z) => z.anzeige)).toEqual(["GEHEIM-MEIN"]);
});
it("lässt sich die Kundengrenze nicht durch einen Suchbegriff aushebeln", async () => {
const { techniker } = await zweiKunden();
// Genau der Fall aus dem Review: die Suche bringt ein eigenes `OR` mit.
expect(await geraeteListe(techniker, { suche: "GEHEIM-FREMD" })).toEqual([]);
expect(await geraeteListe(techniker, { suche: "GEHEIM" })).toHaveLength(1);
expect(await geraeteListe(techniker, { suche: "LZ-BBBBBB" })).toEqual([]);
// Und auch nicht mit Typ- und Archivfilter zusammen.
expect(
await geraeteListe(techniker, { suche: "GEHEIM", auchArchivierte: true, typ: GeraeteTyp.SONSTIGES }),
).toHaveLength(1);
});
it("gibt ein fremdes Gerät auch beim Einzelabruf nicht heraus", async () => {
const { techniker, fremder } = await zweiKunden();
const fremdesGeraet = await testPrisma.geraet.findFirstOrThrow({
where: { kundeId: fremder.id },
});
expect(await geraetHolen(fremdesGeraet.id, techniker)).toBeUndefined();
});
it("schlägt als Besitzer nur aktive Personen sichtbarer Kunden vor", async () => {
const { techniker, meiner, fremder } = await zweiKunden();
await testPrisma.mitarbeiter.createMany({
data: [
{ kundeId: meiner.id, vorname: "A", nachname: "Aktiv", anzeigename: "Anna Aktiv" },
{
kundeId: meiner.id,
vorname: "D",
nachname: "Weg",
anzeigename: "Dieter Weg",
aktiv: false,
},
{ kundeId: fremder.id, vorname: "F", nachname: "Fremd", anzeigename: "Frida Fremd" },
],
});
const auswahl = await besitzerKandidaten(techniker);
expect(auswahl.kandidaten.map((k) => k.anzeigename)).toEqual(["Anna Aktiv"]);
expect(auswahl.gekuerzt).toBe(false);
});
it("nennt bei jedem Kandidaten den Kunden, damit die Auswahl ihn filtern kann", async () => {
/*
* Der Befund aus dem Review: die Seite holt die Kandidaten **einmal** und
* gab sie in jede Karte. Ein Administrator sieht alle Kunden — die
* Auswahlliste bot damit Personen an, die der Dienst danach abweist, weil
* ein Geraet nur einer Person desselben Kunden gehoeren kann. Ohne
* `kundeId` am Kandidaten laesst sich das in der Oberflaeche nicht
* verhindern.
*/
const { meiner, fremder } = await zweiKunden();
const admin = await testPrisma.benutzer.create({
data: { email: "a2@firma.example", name: "Admin", rolle: Rolle.ADMIN },
});
await testPrisma.mitarbeiter.createMany({
data: [
{ kundeId: meiner.id, vorname: "A", nachname: "A", anzeigename: "Anna A" },
{ kundeId: fremder.id, vorname: "B", nachname: "B", anzeigename: "Bert B" },
],
});
const auswahl = await besitzerKandidaten({ id: admin.id, rolle: admin.rolle });
expect(auswahl.kandidaten.map((k) => k.anzeigename)).toEqual(["Anna A", "Bert B"]);
expect(auswahl.kandidaten.map((k) => k.kundeId).sort()).toEqual([meiner.id, fremder.id].sort());
// Was die Oberflaeche daraus macht: nur die des eigenen Kunden.
expect(auswahl.kandidaten.filter((k) => k.kundeId === meiner.id).map((k) => k.anzeigename)).toEqual([
"Anna A",
]);
});
it("sagt, wenn die Besitzerauswahl gekürzt wurde", async () => {
const { techniker, meiner } = await zweiKunden();
await testPrisma.mitarbeiter.createMany({
data: Array.from({ length: 3 }, (_, i) => ({
kundeId: meiner.id,
vorname: "P",
nachname: `Nummer ${i}`,
anzeigename: `Person ${i}`,
})),
});
const auswahl = await besitzerKandidaten(techniker, 2);
expect(auswahl.kandidaten).toHaveLength(2);
expect(auswahl.gekuerzt).toBe(true);
});
});
});