Files
craftvia/scripts/test-events-deferred.ts
T
msolarczekandClaude Opus 5 a4a61dc31f Ereignisse erst nach dem Commit ausliefern
Benachrichtigungs- und Mailversand lief im Handler noch in der offenen Transaktion und
zählte gegen deren Zeitlimit; unter Volllast brachen dadurch wechselnde Tests ab
(test-einsatz-field 31 s, test-planung-recommend). withDeferredEvents puffert Ereignisse
innerhalb von inTransaction und stellt sie nach dem Commit zu, bei Rollback gar nicht —
analog zu withDeferredAudit und passend zum dokumentierten Vertrag in events.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:05:49 +02:00

71 lines
2.7 KiB
TypeScript

// Ereignisse werden erst NACH dem Commit ausgeliefert (events.ts#withDeferredEvents, von
// services/context.ts#inTransaction gesetzt). Vorher lief der Benachrichtigungs- und Mailversand
// noch in der offenen Transaktion und zählte gegen deren Zeitlimit — unter Last brach der
// Einsatzstart deshalb sporadisch ab.
// (1) innerhalb der Transaktion: noch keine Benachrichtigung
// (2) nach dem Commit: Benachrichtigung vorhanden
// (3) Rollback: Ereignis wird verworfen, keine Benachrichtigung
// (4) ohne Transaktion: unverändert sofortige Auslieferung
//
// Lauf: npx tsx scripts/test-events-deferred.ts
import "dotenv/config";
import { randomUUID } from "node:crypto";
import type { DomainEvent } from "../src/lib/events";
import { prisma } from "../src/server/db";
import { emitEvent } from "../src/server/events";
import { inTransaction } from "../src/server/services/context";
import { createTenant, ok, runSuite, section, type TenantFixture } from "./lib/e2e-fixture";
const SLUG = "zz-events-deferred";
const TYPE = "work_order.assigned";
async function order(A: TenantFixture) {
return prisma.workOrder.create({
data: {
tenantId: A.tenantId,
number: `EV-${randomUUID().slice(0, 6)}`,
title: "Heizung warten",
customerId: A.customerId,
siteId: A.siteId,
assignedTeamId: A.teamId,
status: "assigned",
},
});
}
const ev = (entityId: string): DomainEvent => ({ type: TYPE, entityType: "work_order", entityId });
const notifications = (tenantId: string, entityId: string) => prisma.notification.count({ where: { tenantId, type: TYPE, entityId } });
void runSuite("Ereignisse nach dem Commit", [SLUG], async () => {
const A = await createTenant(SLUG);
section("Transaktion mit Commit");
const wo1 = await order(A);
const duringTransaction = await inTransaction(A.ctx.backoffice, async (tx) => {
await emitEvent(tx, ev(wo1.id));
return notifications(A.tenantId, wo1.id);
});
ok(duringTransaction === 0, "(1) während der Transaktion wird noch nichts zugestellt");
ok((await notifications(A.tenantId, wo1.id)) > 0, "(2) nach dem Commit ist die Benachrichtigung da");
section("Rollback");
const wo2 = await order(A);
let rolledBack = false;
try {
await inTransaction(A.ctx.backoffice, async (tx) => {
await emitEvent(tx, ev(wo2.id));
throw new Error("rollback");
});
} catch {
rolledBack = true;
}
ok(rolledBack, "(3) Transaktion abgebrochen");
ok((await notifications(A.tenantId, wo2.id)) === 0, "(3) verworfenes Ereignis löst keine Benachrichtigung aus");
section("Ohne Transaktion");
const wo3 = await order(A);
await emitEvent(A.ctx.backoffice, ev(wo3.id));
ok((await notifications(A.tenantId, wo3.id)) > 0, "(4) ohne Transaktion unverändert sofort zugestellt");
});