Die Oberflaeche legt einen Vorgang an und schickt nur dessen ID in die Warteschlange; der Worker fuehrt ihn aus und haelt Status, Ergebnis oder Fehler fest. So bleiben lange SSH-Vorgaenge aus dem Webprozess heraus, und der Fortschritt ist ohne Job-Interna sichtbar. Kein automatisches Wiederholen: ein halb ausgefuehrter Schreibvorgang auf einem Host gehoert vor einen Menschen. Fehlertexte aus fremden Bibliotheken erreichen die Oberflaeche nie. Dockerfile und Compose fuer den Betrieb: Master-Key als Secret nur im Worker, Datenbank in einem internen Netz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
|
import { prisma } from "@/lib/db";
|
|
import { vorgangEinreihen, vorgangLesen } from "@/server/vorgaenge/vorgaenge";
|
|
import { WARTESCHLANGE, warteschlangeHolen, type VorgangsJob } from "@/server/vorgaenge/warteschlange";
|
|
import { VorgangsFehler, vorgangAusfuehren, type HandlerTabelle } from "@/worker/ausfuehren";
|
|
import { datenbankLeeren } from "./hilfen/db";
|
|
|
|
/** Vorgaenge zwischen Web und Worker (#007). */
|
|
|
|
beforeEach(async () => {
|
|
await datenbankLeeren();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
const boss = await warteschlangeHolen();
|
|
await boss.stop({ graceful: false });
|
|
});
|
|
|
|
async function anlegen() {
|
|
return prisma.vorgang.create({ data: { art: "ERREICHBARKEIT" } });
|
|
}
|
|
|
|
describe("Vorgang ausfuehren", () => {
|
|
it("haelt Ergebnis und Status fest", async () => {
|
|
const vorgang = await anlegen();
|
|
const handler: HandlerTabelle = { ERREICHBARKEIT: async () => ({ ergebnis: { erreichbar: true } }) };
|
|
await vorgangAusfuehren(vorgang.id, handler);
|
|
const fertig = await vorgangLesen(vorgang.id);
|
|
expect(fertig?.status).toBe("ERLEDIGT");
|
|
expect(fertig?.ergebnis).toEqual({ erreichbar: true });
|
|
expect(fertig?.beendetAm).not.toBeNull();
|
|
});
|
|
|
|
it("zeigt nur Meldungen von VorgangsFehlern, alles andere allgemein", async () => {
|
|
const a = await anlegen();
|
|
await vorgangAusfuehren(a.id, {
|
|
ERREICHBARKEIT: async () => {
|
|
throw new VorgangsFehler("Host antwortet nicht.");
|
|
},
|
|
});
|
|
expect((await vorgangLesen(a.id))?.fehler).toBe("Host antwortet nicht.");
|
|
|
|
const b = await anlegen();
|
|
await vorgangAusfuehren(b.id, {
|
|
ERREICHBARKEIT: async () => {
|
|
throw new Error("-----BEGIN OPENSSH PRIVATE KEY----- geheim");
|
|
},
|
|
});
|
|
const fehler = (await vorgangLesen(b.id))?.fehler ?? "";
|
|
expect(fehler).not.toContain("PRIVATE KEY");
|
|
expect(fehler).toMatch(/Interner Fehler/);
|
|
});
|
|
|
|
it("startet einen Vorgang nur einmal", async () => {
|
|
const vorgang = await anlegen();
|
|
let aufrufe = 0;
|
|
const handler: HandlerTabelle = {
|
|
ERREICHBARKEIT: async () => {
|
|
aufrufe += 1;
|
|
return {};
|
|
},
|
|
};
|
|
await Promise.all([vorgangAusfuehren(vorgang.id, handler), vorgangAusfuehren(vorgang.id, handler)]);
|
|
expect(aufrufe).toBe(1);
|
|
});
|
|
|
|
it("meldet fehlende Handler verstaendlich", async () => {
|
|
const vorgang = await anlegen();
|
|
await vorgangAusfuehren(vorgang.id, {});
|
|
expect((await vorgangLesen(vorgang.id))?.fehler).toMatch(/noch keine Ausführung/);
|
|
});
|
|
});
|
|
|
|
describe("Warteschlange", () => {
|
|
it("bringt einen eingereihten Vorgang bis in den Worker", async () => {
|
|
const boss = await warteschlangeHolen();
|
|
const id = await vorgangEinreihen({
|
|
art: "ERREICHBARKEIT",
|
|
angefordertVonId: null,
|
|
eingabe: { probe: 1 },
|
|
});
|
|
// Die Warteschlange ueberlebt das Leeren der Tabellen; deshalb gezielt den eigenen Job suchen.
|
|
const jobs = await boss.fetch<VorgangsJob>(WARTESCHLANGE, { batchSize: 100 });
|
|
const job = jobs.find((kandidat) => kandidat.data.vorgangId === id);
|
|
expect(job?.data).toEqual({ vorgangId: id });
|
|
await vorgangAusfuehren(job!.data.vorgangId, { ERREICHBARKEIT: async () => ({ ergebnis: "ok" }) });
|
|
await boss.complete(WARTESCHLANGE, job!.id);
|
|
expect((await vorgangLesen(id))?.status).toBe("ERLEDIGT");
|
|
});
|
|
});
|