import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { env } from "cloudflare:workers"; import { describe, expect, it } from "vitest"; import migrationInitial from "../migrations/d1/0001_initial.sql?raw"; import migrationMessageIndex from "../migrations/d1/0002_message_index.sql?raw"; import migrationTransactionalRequests from "../migrations/d1/0007_transactional_requests.sql?raw"; import migrationEmailEvents from "../migrations/d1/0008_email_events_suppressions.sql?raw"; import worker from "../src/server"; import { applyMigrations } from "./helpers/migrations"; type TestEnv = Env & { INDEX_DB: D1Database }; type HealthBody = { readiness: { ok: boolean; status: string }; dependencies: { sendingFeedback: { ok: boolean; configured: boolean; mode: string; reason: string | null; darkDomains: string[]; maturityHours: number | null; }; }; }; const testEnv = env as unknown as TestEnv; async function fetchHealth(url: string): Promise { const request = new Request(url); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); return response; } // D1 storage in this pool is NOT rolled back between individual `it()`s within a // file (only across files) -- tables created by one test remain visible to later // tests in this same describe block. So the "schema missing" case must run BEFORE // the migrations are applied, and the "schema present" case (which applies the // migrations) must run after it, permanently establishing the schema for the rest // of this file's tests (the last test below only asserts on `auth`, which is // unaffected by indexDb's state either way). describe("health route", () => { // This is the regression test for the false-confidence bug: previously // `indexDb: { ok: true, configured: true }` was hardcoded and never queried D1, // so a missing migration (e.g. no "aliases" table) was invisible to /api/health // even while email ingest was 500ing with "D1_ERROR: no such table: aliases". it("reports indexDb.ok:false with a reason and degrades readiness when the D1 schema is missing", async () => { const response = await fetchHealth("http://localhost/api/health"); expect(response.status).toBe(200); const body = (await response.json()) as { readiness: { ok: boolean; status: string }; dependencies: { indexDb: { ok: boolean; configured: boolean; reason: string | null } }; }; expect(body.dependencies.indexDb.ok).toBe(false); expect(body.dependencies.indexDb.configured).toBe(true); expect(body.dependencies.indexDb.reason).toBeTruthy(); expect(body.dependencies.indexDb.reason).toContain("aliases"); expect(body.readiness).toEqual({ ok: false, status: "degraded" }); }); it("reports indexDb.ok:true and overall readiness ready when the D1 schema is present", async () => { await applyMigrations( testEnv.INDEX_DB, migrationInitial as string, migrationMessageIndex as string, ); const response = await fetchHealth("http://localhost/api/health"); expect(response.status).toBe(200); expect(await response.json()).toEqual({ ok: true, readiness: { ok: true, status: "ready", }, dependencies: { auth: { ok: true, configured: false, mode: "local-dev-bypass", reason: "Better Auth is disabled until BETTER_AUTH_SECRET is configured.", missing: ["BETTER_AUTH_SECRET"], }, indexDb: { ok: true, configured: true, reason: null, }, cloudflareApi: { ok: true, configured: false, reason: "CLOUDFLARE_API_TOKEN is not set.", }, telegram: { ok: true, configured: false, mode: "off", reason: "Telegram bridge is disabled until TELEGRAM_BOT_TOKEN is set.", missing: [], webhookUrl: null, pendingUpdateCount: null, }, // The transactional projections are not part of this migration set, so // the send log cannot be read at all. "unknown" is the honest answer to // that, and it must not read as "all clear". sendingFeedback: { ok: true, configured: false, mode: "unknown", reason: null, darkDomains: [], maturityHours: null, }, }, }); }); it("reports degraded auth readiness on non-localhost when Access is not configured", async () => { const response = await fetchHealth("https://example.com/api/health"); expect(response.status).toBe(200); const body = (await response.json()) as { readiness: { ok: boolean; status: string }; dependencies: { auth: { ok: boolean; reason: string } }; }; expect(body.readiness).toEqual({ ok: false, status: "degraded" }); expect(body.dependencies.auth.ok).toBe(false); expect(body.dependencies.auth.reason).toBe( "Better Auth is not configured, so non-localhost requests cannot authenticate.", ); }); // The defect this guards: a sending domain with no Email Sending event // subscription produces no lifecycle events for anything, so every send from // it stays `delivery_status: null` and looks exactly like a send whose event // has not arrived yet. Nothing in the system could tell those apart, and three // features read the absence as evidence about the message. it("names a sending domain whose feedback channel has never answered", async () => { await applyMigrations( testEnv.INDEX_DB, migrationTransactionalRequests as string, migrationEmailEvents as string, ); const before = (await (await fetchHealth("http://localhost/api/health")).json()) as HealthBody; expect(before.dependencies.sendingFeedback).toMatchObject({ ok: true, configured: false, mode: "no_sends", }); const old = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(); await insertRequestLog({ requestId: "req-dark", sender: "bot@notify.example.com", at: old }); await insertRequestLog({ requestId: "req-live", sender: "bot@send.example.com", at: old, deliveryEventAt: old, }); const after = (await (await fetchHealth("http://localhost/api/health")).json()) as HealthBody; const feedback = after.dependencies.sendingFeedback; expect(feedback.ok).toBe(false); expect(feedback.mode).toBe("dark"); expect(feedback.darkDomains).toEqual(["notify.example.com"]); expect(feedback.reason).toContain("notify.example.com"); // The domain that is answering must not be dragged down with it: this is a // per-domain fact, and a blanket "sending feedback is broken" would send the // operator looking in the wrong place. expect(feedback.darkDomains).not.toContain("send.example.com"); // Readiness stays ready: sending still works, and a dark feedback channel is // not a reason to fail a liveness probe. expect(after.readiness.ok).toBe(true); }); // Liveness is a claim about now. A domain nobody has sent from in a month has // no current evidence either way, and keeping it permanently red would both // mislead and turn a polled endpoint into an ever-growing table scan. it("ignores a domain whose sends are older than the lookback window", async () => { await insertRequestLog({ requestId: "req-ancient", sender: "bot@retired.example.com", at: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000).toISOString(), }); const body = (await (await fetchHealth("http://localhost/api/health")).json()) as HealthBody; expect(body.dependencies.sendingFeedback.darkDomains).not.toContain("retired.example.com"); expect(body.dependencies.sendingFeedback.darkDomains).toEqual(["notify.example.com"]); }); }); async function insertRequestLog(row: { requestId: string; sender: string; at: string; deliveryEventAt?: string; }): Promise { await testEnv.INDEX_DB.prepare( `INSERT INTO transactional_request_log (request_id, key_id, mailbox_id, status, to_addr, template_id, sender, provider_message_id, error_code, delivery_status, delivery_event_at, created_at, updated_at) VALUES (?, 'key-1', 'mbx-1', 'sent', 'someone@example.org', NULL, ?, ?, NULL, ?, ?, ?, ?)`, ) .bind( row.requestId, row.sender, `msg-${row.requestId}`, row.deliveryEventAt ? "delivered" : null, row.deliveryEventAt ?? null, row.at, row.at, ) .run(); }