/** * 部署配置回归测试(console/wrangler.jsonc): * 这类问题只在生产出现,代码与本地 bun run dev 都测不到,只能锁住配置本身。 * * 背景(2026-09-30 线上事故):compatibility_date >= 2025-04-01 会隐式开启 * assets_navigation_prefers_asset_serving —— 浏览器【导航请求】不进入 Worker, * 直接由 SPA 资源层返回 index.html。于是 /api/oauth/cloudflare/callback 这个 * 浏览器重定向落点被截胡,前端在无匹配路由的路径上 hydrate,渲染 React Router 的 * 404 页面;后端连 state 都没消费,表现为「Cloudflare 授权成功但毫无反应」。 * 修法是 assets.run_worker_first 显式列出 /api/*。 */ import { test, expect } from "bun:test"; /** # 函数 * 读取并解析 wrangler.jsonc(去掉 // 注释与行尾注释) */ function readWranglerConfig(): Record { const raw = require("node:fs").readFileSync( new URL("../console/wrangler.jsonc", import.meta.url).pathname, "utf8", ) as string; const stripped = raw .split("\n") .map((line) => { // 行注释:不在字符串里的 // let out = ""; let inString = false; let hitComment = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (ch === '"') inString = !inString; if (!inString && ch === "/" && line[i + 1] === "/") { hitComment = true; break; } out += ch; } // 整行都是注释 -> 丢弃该行;值后面才跟注释 -> 去掉它带来的行尾逗号 if (out.trim() === "") return ""; return hitComment ? out.replace(/,\s*$/, "") : out; }) .join("\n"); return JSON.parse(stripped) as Record; } const config = readWranglerConfig(); const assets = config.assets as Record; const runWorkerFirst = assets?.run_worker_first as string[] | boolean | undefined; test("assets 开启 SPA 回退时,必须显式让 /api/* 先走 Worker", () => { expect(assets.not_found_handling).toBe("single-page-application"); // 数组形式(而不是 true):静态资源保持 asset-first 快路径 expect(Array.isArray(runWorkerFirst)).toBe(true); const patterns = runWorkerFirst as string[]; expect(patterns).toContain("/api/*"); // 内部接口(Email Worker 两阶段接收)同理,不能被 SPA 资源层吃掉 expect(patterns).toContain("/internal/*"); }); test("OAuth 回调路径确实落在 run_worker_first 覆盖范围内", () => { // 与 console/src/services/cfapi.ts 的 CF_OAUTH_CALLBACK_PATH 对应: // 它是浏览器跨站重定向回来的落点,必须由 Worker 处理而不是资源层 const patterns = runWorkerFirst as string[]; const matches = (path: string) => patterns.some((p) => p.endsWith("/*") && path.startsWith(p.slice(0, -1))); expect(matches("/api/oauth/cloudflare/callback")).toBe(true); expect(matches("/api/health")).toBe(true); expect(matches("/internal/email/authorize")).toBe(true); });