Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/services/admin/webhook-inbox.server.test.ts
T

113 lines
4.4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { getRlsExecutionContext } from "@hivegrid/db/rls-context.server";
import { AdminWebhookInboxError, requestWebhookReplay, type AdminWebhookInboxDeps } from "./webhook-inbox.server";
function makeAttempt(overrides: Record<string, unknown> = {}) {
return {
id: "attempt_old",
endpoint: "https://example.test/webhook",
success: false,
status: 500,
eventId: "event_1",
event: {
id: "event_1",
name: "billing.mollie.webhook.failed",
data: JSON.stringify({ paymentId: "tr_123", status: "failed" }),
resource: "billing",
tenantId: "tenant_1",
},
...overrides,
};
}
function makeDeps(attempt = makeAttempt(), queue: { add: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> } | null = { add: vi.fn(async () => ({ id: "job_1" })), close: vi.fn(async () => {}) }) {
const recordAction = vi.fn(async () => {});
const createdAttempts: unknown[] = [];
const db = {
eventWebhookAttempt: {
findUnique: vi.fn(async () => attempt),
create: vi.fn(async (args) => {
createdAttempts.push(args.data);
return { id: "attempt_replay" };
}),
update: vi.fn(async () => ({})),
},
};
const deps = {
db,
now: vi.fn(() => new Date("2026-07-09T10:00:00.000Z")),
createQueue: vi.fn(async () => queue),
enqueue: vi.fn(async (targetQueue, descriptor, options) => {
await targetQueue.add(descriptor.jobName, descriptor.payload, {});
return { id: options?.idempotencyKey ?? "job_1" };
}),
recordAction,
} as unknown as AdminWebhookInboxDeps;
return { deps, db, queue, recordAction, createdAttempts };
}
describe("requestWebhookReplay", () => {
it("creates a fresh attempt, enqueues webhook delivery, and audits the replay request", async () => {
const { deps, db, queue, recordAction, createdAttempts } = makeDeps();
db.eventWebhookAttempt.create.mockImplementationOnce(async (args) => {
expect(getRlsExecutionContext()).toMatchObject({ tenantId: "tenant_1" });
createdAttempts.push(args.data);
return { id: "attempt_replay" };
});
await expect(
requestWebhookReplay({
attemptId: "attempt_old",
reason: "Provider Timeout erneut ausliefern",
actorUserId: "admin_1",
actorEmail: "admin@hivegrid.de",
}, deps)
).resolves.toEqual({ replayAttemptId: "attempt_replay" });
expect(db.eventWebhookAttempt.findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "attempt_old" } }));
expect(createdAttempts[0]).toMatchObject({
eventId: "event_1",
endpoint: "https://example.test/webhook",
message: "admin.webhooks.messages.replayRequested",
});
expect(deps.enqueue).toHaveBeenCalledWith(
queue,
expect.objectContaining({
jobName: "deliver-webhook",
payload: expect.objectContaining({
url: "https://example.test/webhook",
tenantId: "tenant_1",
body: { paymentId: "tr_123", status: "failed" },
metadata: expect.objectContaining({ legacyAttemptId: "attempt_replay", adminReplayOfAttemptId: "attempt_old" }),
}),
}),
expect.objectContaining({ idempotencyKey: "attempt_replay", tenantId: "tenant_1" })
);
expect(queue?.close).toHaveBeenCalledOnce();
expect(recordAction).toHaveBeenCalledWith(expect.objectContaining({ action: "webhook.replayRequested", targetId: "attempt_old" }));
});
it("rejects delivered attempts so operators cannot duplicate successful webhooks", async () => {
const { deps } = makeDeps(makeAttempt({ success: true, status: 204 }));
await expect(
requestWebhookReplay({ attemptId: "attempt_old", reason: "Bitte erneut senden", actorUserId: "admin_1" }, deps)
).rejects.toMatchObject({ code: "REPLAY_NOT_ALLOWED" });
});
it("marks the fresh attempt failed when the queue is unavailable", async () => {
const { deps, db } = makeDeps(makeAttempt(), null);
await expect(
requestWebhookReplay({ attemptId: "attempt_old", reason: "Provider Timeout erneut ausliefern", actorUserId: "admin_1" }, deps)
).rejects.toBeInstanceOf(AdminWebhookInboxError);
expect(db.eventWebhookAttempt.update).toHaveBeenCalledWith(
expect.objectContaining({
where: { id: "attempt_replay" },
data: expect.objectContaining({ success: false, status: 503, message: "admin.webhooks.messages.queueUnavailable" }),
})
);
});
});