126 lines · 5.6 KB
#!/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();