scripts/smoke-email-local.ts
Rama main · réplica de lectura del repositorio original.
import { existsSync, readFileSync } from "node:fs";
const baseUrlArg = process.argv[2];
const fixturePath = process.argv[3] ?? "fixtures/mime/simple-text.eml";
if (!baseUrlArg) {
console.error("Usage: pnpm smoke:email:local <http-base-url> [fixture]");
process.exit(1);
}
const baseUrl: string = baseUrlArg;
// The /api/debug/phase0/* routes fail closed (404) unless the caller presents the same
// PHASE0_DEBUG_TOKEN the Worker was started with. Prefer an explicit env var (CI sets one);
// otherwise fall back to reading the local `.dev.vars` file (auto-generated by `pnpm dev`
// via scripts/ensure-dev-vars.ts, see README quickstart) so this script keeps working with
// zero extra manual steps.
function readDebugToken(): string | undefined {
if (process.env.PHASE0_DEBUG_TOKEN) {
return process.env.PHASE0_DEBUG_TOKEN;
}
if (!existsSync(".dev.vars")) {
return undefined;
}
const content = readFileSync(".dev.vars", "utf8");
for (const line of content.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const separator = trimmed.indexOf("=");
if (separator === -1) continue;
const key = trimmed.slice(0, separator).trim();
if (key === "PHASE0_DEBUG_TOKEN") {
return trimmed.slice(separator + 1).trim();
}
}
return undefined;
}
const debugToken = readDebugToken();
const debugHeaders: HeadersInit | undefined = debugToken
? { "x-phase0-debug-token": debugToken }
: undefined;
const raw = readFileSync(fixturePath);
const to = "test@example.com";
const from = "sender@example.com";
const emailUrl = new URL("/cdn-cgi/handler/email", baseUrl);
emailUrl.searchParams.set("from", from);
emailUrl.searchParams.set("to", to);
async function postFixture(label: string) {
const response = await fetch(emailUrl, {
method: "POST",
body: raw,
});
const text = await response.text();
console.log(`${label}:`, text);
if (!response.ok) {
throw new Error(`${label} failed with ${response.status}`);
}
}
async function fetchDebug(baseUrl: string) {
const mailboxResponse = await fetch(new URL("/api/debug/phase0/test-mailbox-id", baseUrl), {
headers: debugHeaders,
});
const { mailboxId } = (await mailboxResponse.json()) as { mailboxId: string };
const response = await fetch(new URL(`/api/debug/phase0/mailboxes/${mailboxId}`, baseUrl), {
headers: debugHeaders,
});
if (!response.ok) {
throw new Error(`debug failed with ${response.status}: ${await response.text()}`);
}
return (await response.json()) as {
messageCount: number;
messages: Array<{
raw_r2_key: string;
raw_sha256: string;
idempotency_key: string;
subject: string | null;
}>;
};
}
async function waitForMessageCount(expected: number) {
const deadline = Date.now() + 10_000;
let lastDebug: Awaited<ReturnType<typeof fetchDebug>> | null = null;
while (Date.now() < deadline) {
lastDebug = await fetchDebug(baseUrl);
if (lastDebug.messageCount === expected) {
return lastDebug;
}
await new Promise((resolve) => setTimeout(resolve, 300));
}
throw new Error(`messageCount did not become ${expected}; last=${JSON.stringify(lastDebug)}`);
}
await postFixture("first-delivery");
const firstDebug = await waitForMessageCount(1);
const message = firstDebug.messages[0];
if (!message) {
throw new Error("missing message after first delivery");
}
const r2HeadUrl = new URL("/api/debug/phase0/r2/head", baseUrl);
r2HeadUrl.searchParams.set("key", message.raw_r2_key);
const r2Head = (await (await fetch(r2HeadUrl, { headers: debugHeaders })).json()) as {
exists?: boolean;
};
console.log("r2-head:", JSON.stringify(r2Head));
if (!r2Head.exists) {
throw new Error(`R2 object missing: ${message.raw_r2_key}`);
}
await postFixture("duplicate-delivery");
const duplicateDebug = await waitForMessageCount(1);
console.log("debug:", JSON.stringify(duplicateDebug));
console.log(
"queue-payload-sample:",
JSON.stringify({
eventType: "email.received.v1",
mailboxId: message.idempotency_key.split(":")[2] ?? "unknown",
rawR2Key: message.raw_r2_key,
rawSha256: message.raw_sha256,
idempotencyKey: message.idempotency_key,
}),
);
console.log("PASS: local email smoke completed with one DO message after duplicate delivery");