From 772ce80ad27ab483fb36ea5bdefe8a14b71228ee Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 18:33:39 +0800 Subject: [PATCH] docs(tests): clarify API smoke timing and proxy assertions --- tests/api-smoke.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/api-smoke.mjs b/tests/api-smoke.mjs index 715da72..9a0a02e 100644 --- a/tests/api-smoke.mjs +++ b/tests/api-smoke.mjs @@ -142,6 +142,7 @@ app.stdout.on("data", (x) => (startup += x)); app.stderr.on("data", (x) => (startup += x)); try { // 最多轮询 100 次,失败后等待 100ms,再检查首页是否可用;不能只凭 spawn 返回就宣称服务已启动。 + // 单次 fetch 没有 AbortSignal 截止时间,因此 100 次是尝试次数上限,不等于严格十秒的启动总超时。 let ready = false; for (let n = 0; n < 100; n++) { if (app.exitCode !== null) @@ -200,9 +201,10 @@ try { assert(cookie?.includes("HttpOnly")); assert(cookie.includes("SameSite=Lax")); assert(cookie.includes("Max-Age=2592000")); + // Node fetch 不在此维护浏览器 cookie jar;从登录响应取 name=value 后,后续受认证请求显式携带 Cookie。 const session = cookie.split(";")[0]; - // Hold the upstream tail: snapshot headers and its first chunk must arrive - // before the proxy has the full JSON body (the old buffering path deadlocks). + // 上游尾部等 releaseStream 才返回,先取响应头和首块,再放行尾部;整体缓冲代理会在此形成循环等待。 + // 2500ms 的 Promise.race 只限制 fetch 返回响应头,后面的 reader.read 不在该 deadline 内,不能据此声称首块也受同一计时约束。 let streamTimer; try { const streamed = await Promise.race([fetch(host + "/api/foundry/workspaces/space/materials/stream-board/canvas", { headers: { Cookie: session } }), new Promise((_, reject) => { streamTimer = setTimeout(() => reject(new Error("Snapshot proxy buffered the body")), 2500); })]); @@ -236,6 +238,7 @@ try { assert.equal((await fetch(host + "/api/foundry/workspaces/space/assets/bad", { headers: { Cookie: session } })).status, 502); const form = new FormData(); form.set("image", new Blob([new Uint8Array([137,80,78,71])], { type: "image/png" }), "test.png"); const upload = await (await fetch(host + "/api/foundry/workspaces/space/assets", { method: "POST", headers: { Cookie: session, Origin: host, "Idempotency-Key": idem }, body: form })).json(); + // 这里只核对 multipart Content-Type、image 字段标记及幂等键;未解析 multipart,也未逐字节比较上传文件内容。 assert.match(upload.type, /^multipart\/form-data; boundary=/); assert.equal(upload.key, idem); assert.ok(upload.multipart); // 有效会话首页重定向且不缓存私有结果;dashboard 初始 HTML 不应包含画布或 landing。 const signedInHome = await fetch(host + "/", { headers: { Cookie: session }, redirect: "manual" }); @@ -319,6 +322,7 @@ try { assert.deepEqual(renamed.content, stored.content); // workspace/board 各自使用 ID 或 slug 的四种组合都应定位同一图谱和独立画布版本。 + // 替身通过 canonical 字符串替换提供该映射;此处验证代理接受四种路径,不验证真实 Foundry 的 slug 解析实现。 for (const workspace of ["space", "creative-space"]) for (const board of ["foundry-generated-id", "my-board"]) { const path = `/api/foundry/workspaces/${workspace}/materials/${board}`; const material = await (await fetch(host + path, { headers: { Cookie: session } })).json();