Files
ProxLedger/tests/host-stilllegen.test.ts
florian.wildeandClaude Opus 5 3657b60d16 feat(hosts): Doppelte Hosts erkennen und stilllegen (#041)
Zwei Eintraege desselben Namens beim selben Kunden entstehen, wenn sich die
Kennung aus /etc/machine-id aendert - Neuinstallation, geklonter Container,
zurueckgespielte Sicherung. Dieselbe Maschine meldet sich dann als neuer Host,
der alte Eintrag bleibt stehen. Zwei getrennte Maschinen mit gleichem Namen
sind aber genauso moeglich.

Neue dreizehnte Warnregel meldet den Fall und nennt die IP-Adressen zum
Unterscheiden; gleiche Namen bei verschiedenen Kunden bleiben still.

Dazu die bis dahin fehlende Moeglichkeit, einen Host loszuwerden: Stilllegen
blendet ihn aus Listen, Zahlen, Suche und Warnungen aus und schliesst seine
offenen Warnungen, laesst Laufzeiten und Rechnungen aber unberuehrt.
Endgueltig loeschen geht nur ohne Laufzeiten - Buchhaltung wirft man nicht weg.

Kein automatisches Zusammenfuehren: Es muesste raten, welcher Eintrag welche
Rechnung erbt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 17:57:18 +02:00

98 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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 { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/** Hosts stilllegen statt löschen – und löschen nur, wenn nichts daran hängt (#041). */
vi.mock("@/server/auth/sitzungspflicht", () => ({
sitzungErforderlich: async () => ({ benutzerId: "test", rolle: "ADMIN", kundeId: null }),
sichtbarerKunde: async () => null,
darfEinkaufSehen: async () => true,
angemeldet: async () => null,
}));
vi.mock("next/cache", () => ({ revalidatePath: () => {} }));
vi.mock("next/navigation", () => ({ redirect: () => {} }));
const { hostArchivieren, hostLoeschen } = await import("@/server/actions/hosts");
const { hostListe } = await import("@/server/queries/hostliste");
function formular(werte: Record<string, string>) {
const daten = new FormData();
for (const [name, wert] of Object.entries(werte)) daten.set(name, wert);
return daten;
}
async function host(hostname = "hv-pve-01") {
return testPrisma.host.create({ data: { agentId: `a-${hostname}`, hostname, produkt: "PVE" } });
}
describe.runIf(datenbankVerfuegbar)("Hosts stilllegen", () => {
beforeEach(async () => {
await datenbankLeeren();
});
afterAll(async () => {
await testPrisma.$disconnect();
});
it("nimmt einen stillgelegten Host aus der Liste", async () => {
const h = await host();
expect((await hostListe({})).gesamt).toBe(1);
await hostArchivieren(formular({ hostId: h.id }));
expect((await hostListe({})).gesamt).toBe(0);
expect((await hostListe({ archiviert: "ja" })).gesamt).toBe(1);
});
it("schließt beim Stilllegen die offenen Warnungen", async () => {
const h = await host();
await testPrisma.alert.create({
data: {
regel: "status-nicht-aktiv",
fingerprint: `status-nicht-aktiv:${h.id}`,
schwere: "WARNUNG",
titel: "Keine Subscription gefunden",
beschreibung: "…",
hostId: h.id,
},
});
await hostArchivieren(formular({ hostId: h.id }));
const alert = await testPrisma.alert.findFirstOrThrow({ where: { hostId: h.id } });
expect(alert.erledigtAm).not.toBeNull();
});
it("nimmt einen Host auf Wunsch wieder auf", async () => {
const h = await host();
await hostArchivieren(formular({ hostId: h.id }));
await hostArchivieren(formular({ hostId: h.id }));
expect((await hostListe({})).gesamt).toBe(1);
});
it("löscht einen Host ohne Laufzeiten", async () => {
const h = await host();
await hostLoeschen(formular({ hostId: h.id }));
expect(await testPrisma.host.count()).toBe(0);
});
it("löscht keinen Host, an dem eine Laufzeit hängt", async () => {
const h = await host();
await testPrisma.laufzeit.create({
data: {
hostId: h.id,
key: "pve1c-aaaa",
start: new Date("2026-01-01"),
ende: new Date("2026-12-31"),
status: "BESTAETIGT",
},
});
await hostLoeschen(formular({ hostId: h.id }));
// Buchhaltung bleibt: Der Host ist noch da und lässt sich nur stilllegen.
expect(await testPrisma.host.count()).toBe(1);
});
});