From 4e9029223a2d64b4c67f223dd8ce592b58063795 Mon Sep 17 00:00:00 2001 From: huangyinghui <18666119673@163.com> Date: Tue, 8 Sep 2026 20:42:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20CF=20API=20=E7=A9=BA=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E4=BD=93=E6=8C=89=202xx=20=E8=AE=A1=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E2=80=94=E2=80=94workers/domains=20DELETE=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=20200=20=E6=97=A0=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机第三发现:解绑 Custom Domain 的 DELETE 请求返回 200 空 body, res.json() 抛 'Unexpected end of JSON input' 中断部署(core 主域的 解绑已生效、hello 的未执行,半清理状态)。toResult 统一处理:空体按 状态码判定,非 JSON 体降级为带说明的错误。 Signed-off-by: huangyinghui <18666119673@163.com> --- deploy/cloudflare/src/dns.ts | 15 +++++++++++++-- deploy/cloudflare/test/dns.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/deploy/cloudflare/src/dns.ts b/deploy/cloudflare/src/dns.ts index 2e3f0ad..f4e2728 100644 --- a/deploy/cloudflare/src/dns.ts +++ b/deploy/cloudflare/src/dns.ts @@ -23,14 +23,25 @@ interface CfResult { result?: unknown; } +/** 空响应体视为 2xx 成功(CF 部分 API 返回 200/204 无 body),JSON 解析失败则按状态码判断。 */ +async function toResult(res: Response): Promise { + const text = await res.text(); + if (!text.trim()) return { success: res.ok }; + try { + return (JSON.parse(text)) as CfResult; + } catch { + return { success: res.ok, errors: [{ code: 0, message: `非 JSON 响应:${text.slice(0, 80)}` }] }; + } +} + async function cfGet(url: string, token: string): Promise { const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }); - return (await res.json()) as CfResult; + return toResult(res); } async function cfDelete(url: string, token: string): Promise { const res = await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` } }); - return (await res.json()) as CfResult; + return toResult(res); } async function cfPost(url: string, token: string, body: unknown): Promise { diff --git a/deploy/cloudflare/test/dns.test.ts b/deploy/cloudflare/test/dns.test.ts index faf431f..32dac55 100644 --- a/deploy/cloudflare/test/dns.test.ts +++ b/deploy/cloudflare/test/dns.test.ts @@ -94,6 +94,32 @@ describe('removeLegacyCustomDomains(B 方案迁移:解绑同域遗留 Custom await removeLegacyCustomDomains({ accountId: 'acc-1', domain: 'demo.handywote.top', apiToken: 'tok' }); expect(calls.some((c) => c.method === 'DELETE')).toBe(false); }); + + it('DELETE 返回空 body(真机:200 无体)→ 仍计成功并记录日志', async () => { + // stubCf 只能回放 JSON——这里直接手工 stub 空体响应 + const rawFetch = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { + const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url; + if (url.endsWith('/workers/domains')) { + return new Response(JSON.stringify({ + success: true, + result: [{ id: 'd1', hostname: 'demo.handywote.top' }], + }), { status: 200 }); + } + if (init?.method === 'DELETE') { + return new Response(null, { status: 200 }); // 空体 + } + return new Response('{}', { status: 200 }); + }); + vi.stubGlobal('fetch', rawFetch); + const logs: string[] = []; + await removeLegacyCustomDomains({ + accountId: 'acc-1', + domain: 'demo.handywote.top', + apiToken: 'tok', + log: (m) => logs.push(m), + }); + expect(logs.filter((l) => l.includes('已解绑'))).toHaveLength(1); + }); }); describe('ensureZoneRecord(幂等 DNS 自愈)', () => {