Refine workspaces and event publishing; harden uploads and email delivery
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { createHmac } from "node:crypto";
|
||||
import { and, eq, inArray } from "drizzle-orm";
|
||||
import { emailDeliveries, emailWebhookEvents, events, getDb, groups } from "@album/database";
|
||||
import { emailDeliveryOutcome, recordEmailWebhook, verifyEmailWebhook } from "./webhooks";
|
||||
|
||||
const key = Buffer.from("local-test-signing-secret-not-production");
|
||||
const secret = `whsec_${key.toString("base64")}`;
|
||||
function signed(type = "email.delivered", timestamp = Math.floor(Date.now() / 1000)) {
|
||||
const id = `test_${crypto.randomUUID()}`;
|
||||
const payload = JSON.stringify({ type, created_at: new Date().toISOString(), data: { email_id: crypto.randomUUID() } });
|
||||
const signature = createHmac("sha256", key).update(`${id}.${timestamp}.${payload}`).digest("base64");
|
||||
return { payload, headers: new Headers({ "svix-id": id, "svix-timestamp": `${timestamp}`, "svix-signature": `v1,${signature}` }) };
|
||||
}
|
||||
test("webhooks require an authentic, fresh, unmodified signature", () => {
|
||||
const input = signed();
|
||||
expect(verifyEmailWebhook(input.payload, input.headers, secret)?.outcome).toBe("delivered");
|
||||
expect(() => verifyEmailWebhook(input.payload + " ", input.headers, secret)).toThrow();
|
||||
expect(() => verifyEmailWebhook(input.payload, new Headers(), secret)).toThrow();
|
||||
const stale = signed("email.delivered", 1);
|
||||
expect(() => verifyEmailWebhook(stale.payload, stale.headers, secret)).toThrow();
|
||||
const ignored = signed("email.opened");
|
||||
expect(verifyEmailWebhook(ignored.payload, ignored.headers, secret)).toBeNull();
|
||||
});
|
||||
|
||||
test.skipIf(process.env.WEBHOOK_INTEGRATION !== "1")("early, duplicate and reordered callbacks preserve terminal outcome and event scope", async () => {
|
||||
if (!["localhost", "127.0.0.1"].includes(new URL(process.env.DATABASE_URL!).hostname)) throw new Error("Local database required");
|
||||
const db = getDb();
|
||||
const [group] = await db.select({ id: groups.id }).from(groups).limit(1);
|
||||
const input = signed("email.bounced");
|
||||
const callback = verifyEmailWebhook(input.payload, input.headers, secret)!;
|
||||
const ids = [callback.id, `${callback.id}-delivered`];
|
||||
const [event] = await db.insert(events).values({ groupId: group!.id, title: "Webhook test", slug: `webhook-${crypto.randomUUID()}` }).returning();
|
||||
try {
|
||||
await Promise.all([recordEmailWebhook(callback), recordEmailWebhook(callback)]);
|
||||
expect(await db.select().from(emailWebhookEvents).where(eq(emailWebhookEvents.id, callback.id))).toHaveLength(1);
|
||||
const [delivery] = await db.insert(emailDeliveries).values({ eventId: event!.id, recipient: "webhook@manyangles.test", provider: "resend", payload: { to: "webhook@manyangles.test", from: "test@manyangles.test", subject: "Test", html: "", text: "", referenceId: "test" } }).returning();
|
||||
const scope = and(eq(emailDeliveries.eventId, event!.id), eq(emailDeliveries.id, delivery!.id));
|
||||
expect((await db.select({ outcome: emailDeliveryOutcome }).from(emailDeliveries).where(scope))[0]?.outcome).toBeNull();
|
||||
await db.update(emailDeliveries).set({ providerId: callback.providerId }).where(scope);
|
||||
await recordEmailWebhook({ ...callback, id: ids[1]!, outcome: "delivered", occurredAt: new Date(Date.now() + 1000) });
|
||||
expect((await db.select({ outcome: emailDeliveryOutcome }).from(emailDeliveries).where(scope))[0]?.outcome).toBe("bounced");
|
||||
expect(await db.select({ outcome: emailDeliveryOutcome }).from(emailDeliveries).where(eq(emailDeliveries.eventId, crypto.randomUUID()))).toHaveLength(0);
|
||||
await db.update(emailDeliveries).set({ provider: "mailpit" }).where(scope);
|
||||
expect((await db.select({ outcome: emailDeliveryOutcome }).from(emailDeliveries).where(scope))[0]?.outcome).toBeNull();
|
||||
} finally {
|
||||
await db.delete(events).where(eq(events.id, event!.id));
|
||||
await db.delete(emailWebhookEvents).where(inArray(emailWebhookEvents.id, ids));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user