#!/usr/bin/env bun
/**
* End-to-end check for integrations: signs up, creates an org, adds a generic webhook integration that
* points at a local sink, sends a test event and verifies the signed delivery arrives and is logged.
* Also checks that a private-network URL is rejected (SSRF guard).
*
* The target server must run with INTEGRATIONS_ALLOW_LOOPBACK=true (in .dev.vars) so the loopback sink
* is reachable; production never sets it.
*
* E2E_BASE_URL=http://localhost:3000 bun run test:e2e:integrations
*/
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
import { chromium } from "playwright-core";
const BASE_URL = process.env.E2E_BASE_URL ?? "http://localhost:3000";
const SINK_PORT = Number(process.env.E2E_SINK_PORT ?? 4545);
const TIMEOUT_MS = 60_000;
const DUPLICATE_WINDOW_MS = 20_000;
type Received = { headers: Record<string, string>; body: string };
const received: Received[] = [];
const sink = Bun.serve({
port: SINK_PORT,
hostname: "127.0.0.1",
async fetch(request) {
received.push({ headers: Object.fromEntries(request.headers), body: await request.text() });
return new Response("ok");
},
});
function fail(message: string): never {
console.error(`FAIL: ${message}`);
process.exit(1);
}
async function waitFor<T>(what: string, check: () => T | undefined | Promise<T | undefined>): Promise<T> {
const deadline = Date.now() + TIMEOUT_MS;
while (Date.now() < deadline) {
const value = await check();