Kunden und Standorte pflegen, nicht zugeordnete Hosts in einem Schritt zuweisen, Enrollment- und Host-Tokens verwalten, Audit-Log. Dashboard, Kundenseiten und globale Suche laufen jetzt auf der Datenbank. 45 Tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
166 lines
5.8 KiB
TypeScript
166 lines
5.8 KiB
TypeScript
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
|
import { hostsZuweisenService, ZuweisungsFehler } from "@/server/services/zuweisung";
|
|
import { kennzahlen, hostsOhneKunde } from "@/server/queries/hosts";
|
|
import { kundenListe, offeneRechnungspunkteProKunde } from "@/server/queries/kunden";
|
|
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
|
|
|
|
async function kundeMitStandort(name = "Testkunde GmbH", nummer = "K-7001") {
|
|
return testPrisma.kunde.create({
|
|
data: {
|
|
name,
|
|
kundennummer: nummer,
|
|
standorte: { create: [{ name: "Zentrale" }] },
|
|
},
|
|
include: { standorte: true },
|
|
});
|
|
}
|
|
|
|
async function host(agentId: string, hostname: string, daten: Record<string, unknown> = {}) {
|
|
return testPrisma.host.create({
|
|
data: { agentId, hostname, produkt: "PVE", ...daten },
|
|
});
|
|
}
|
|
|
|
describe.runIf(datenbankVerfuegbar)("Hostzuweisung", () => {
|
|
beforeEach(async () => {
|
|
await datenbankLeeren();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await testPrisma.$disconnect();
|
|
});
|
|
|
|
it("weist mehrere Hosts gleichzeitig einem Kunden und Standort zu", async () => {
|
|
const kunde = await kundeMitStandort();
|
|
const eins = await host("agent-1", "pve-01");
|
|
const zwei = await host("agent-2", "pve-02");
|
|
await host("agent-3", "pve-03");
|
|
|
|
const ergebnis = await hostsZuweisenService([eins.id, zwei.id], kunde.id, kunde.standorte[0].id);
|
|
|
|
expect(ergebnis.anzahl).toBe(2);
|
|
expect(ergebnis.kundenName).toBe("Testkunde GmbH");
|
|
|
|
const zugewiesen = await testPrisma.host.findMany({ where: { kundeId: kunde.id } });
|
|
expect(zugewiesen.map((h) => h.hostname).sort()).toEqual(["pve-01", "pve-02"]);
|
|
expect(zugewiesen.every((h) => h.standortId === kunde.standorte[0].id)).toBe(true);
|
|
|
|
const offen = await hostsOhneKunde();
|
|
expect(offen.map((h) => h.hostname)).toEqual(["pve-03"]);
|
|
});
|
|
|
|
it("schreibt einen Audit-Eintrag", async () => {
|
|
const kunde = await kundeMitStandort();
|
|
const eins = await host("agent-1", "pve-01");
|
|
|
|
await hostsZuweisenService([eins.id], kunde.id, null);
|
|
|
|
const eintrag = await testPrisma.auditEintrag.findFirst({ where: { aktion: "host.zugewiesen" } });
|
|
expect(eintrag).not.toBeNull();
|
|
expect(eintrag?.aenderung).toMatchObject({ anzahl: 1, kundeId: kunde.id });
|
|
});
|
|
|
|
it("schließt die Warnung „Host ohne Kunde“ mit der Zuweisung", async () => {
|
|
const kunde = await kundeMitStandort();
|
|
const eins = await host("agent-1", "pve-01");
|
|
await testPrisma.alert.create({
|
|
data: {
|
|
regel: "host-ohne-kunde",
|
|
fingerprint: `host-ohne-kunde:${eins.id}`,
|
|
schwere: "INFO",
|
|
titel: "Host ist keinem Kunden zugewiesen",
|
|
beschreibung: "pve-01",
|
|
hostId: eins.id,
|
|
},
|
|
});
|
|
|
|
await hostsZuweisenService([eins.id], kunde.id, null);
|
|
|
|
const alert = await testPrisma.alert.findFirstOrThrow({ where: { hostId: eins.id } });
|
|
expect(alert.erledigtAm).not.toBeNull();
|
|
});
|
|
|
|
it("lehnt einen Standort ab, der zu einem anderen Kunden gehört", async () => {
|
|
const kundeA = await kundeMitStandort("Kunde A", "K-7001");
|
|
const kundeB = await kundeMitStandort("Kunde B", "K-7002");
|
|
const eins = await host("agent-1", "pve-01");
|
|
|
|
await expect(hostsZuweisenService([eins.id], kundeA.id, kundeB.standorte[0].id)).rejects.toThrow(
|
|
ZuweisungsFehler,
|
|
);
|
|
|
|
const unveraendert = await testPrisma.host.findUniqueOrThrow({ where: { id: eins.id } });
|
|
expect(unveraendert.kundeId).toBeNull();
|
|
});
|
|
|
|
it("lehnt eine Zuweisung ohne Auswahl oder ohne Kunde ab", async () => {
|
|
const kunde = await kundeMitStandort();
|
|
await expect(hostsZuweisenService([], kunde.id, null)).rejects.toThrow(ZuweisungsFehler);
|
|
await expect(hostsZuweisenService(["irgendwas"], null, null)).rejects.toThrow(ZuweisungsFehler);
|
|
});
|
|
|
|
it("zählt Kennzahlen für Dashboard und Sidebar korrekt", async () => {
|
|
const jetzt = new Date("2026-09-17T12:00:00Z");
|
|
const kunde = await kundeMitStandort();
|
|
|
|
await host("agent-1", "pve-aktiv", {
|
|
kundeId: kunde.id,
|
|
subStatus: "ACTIVE",
|
|
subNextduedate: new Date("2026-10-01"),
|
|
letzterReportAm: new Date("2026-09-17T03:00:00Z"),
|
|
});
|
|
await host("agent-2", "pve-abgelaufen", {
|
|
kundeId: kunde.id,
|
|
subStatus: "EXPIRED",
|
|
letzterReportAm: new Date("2026-09-17T03:00:00Z"),
|
|
});
|
|
await host("agent-3", "pve-stumm", {
|
|
subStatus: "ACTIVE",
|
|
letzterReportAm: new Date("2026-09-10T03:00:00Z"),
|
|
});
|
|
|
|
const zahlen = await kennzahlen(jetzt);
|
|
|
|
expect(zahlen.hostsGesamt).toBe(3);
|
|
expect(zahlen.aktivLizenziert).toBe(2);
|
|
expect(zahlen.ohneSubscription).toBe(1);
|
|
expect(zahlen.baldAblaufend).toBe(1);
|
|
expect(zahlen.ohneKunde).toBe(1);
|
|
expect(zahlen.ohneMeldung).toBe(1);
|
|
});
|
|
|
|
it("zählt offene Rechnungspunkte je Kunde", async () => {
|
|
const kunde = await kundeMitStandort();
|
|
const eins = await host("agent-1", "pve-01", { kundeId: kunde.id });
|
|
|
|
const rechnung = await testPrisma.proxmoxRechnung.create({
|
|
data: {
|
|
nummer: "PX-TEST-9",
|
|
datum: new Date("2026-09-01"),
|
|
nettoBetrag: 115,
|
|
bezahlt: false,
|
|
positionen: { create: [{ bezeichnung: "Key", nettoBetrag: 115 }] },
|
|
},
|
|
include: { positionen: true },
|
|
});
|
|
|
|
await testPrisma.laufzeit.create({
|
|
data: {
|
|
hostId: eins.id,
|
|
key: "pve1c-test",
|
|
start: new Date("2026-09-24"),
|
|
ende: new Date("2027-09-23"),
|
|
status: "BESTAETIGT",
|
|
proxmoxPositionId: rechnung.positionen[0].id,
|
|
},
|
|
});
|
|
|
|
const punkte = await offeneRechnungspunkteProKunde();
|
|
// Einkauf unbezahlt und Kundenrechnung fehlt = zwei offene Punkte von zwei möglichen.
|
|
expect(punkte.get(kunde.id)).toEqual({ offen: 2, gesamt: 2 });
|
|
|
|
const liste = await kundenListe();
|
|
expect(liste[0].hosts).toBe(1);
|
|
});
|
|
});
|