From 3698ff39b4be059cbf520f78ae978cf27f5b20fa Mon Sep 17 00:00:00 2001 From: yushen Date: Thu, 21 May 2026 17:40:10 +0800 Subject: [PATCH] fix(api): correct deleteCloudRuntimeNode contract to match server - Change from DELETE /api/cloud-runtime/nodes/:nodeId (no body) to DELETE /api/cloud-runtime/nodes with JSON body { id: nodeId } - Use fetchRaw + Content-Type header to match server's withBody proxy - Add contract test verifying URL, method, body, and Content-Type Fixes review feedback on MUL-2510 Co-authored-by: multica-agent --- packages/core/api/client.test.ts | 21 +++++++++++++++++++++ packages/core/api/client.ts | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/core/api/client.test.ts b/packages/core/api/client.test.ts index c63db3838..e665382b4 100644 --- a/packages/core/api/client.test.ts +++ b/packages/core/api/client.test.ts @@ -234,6 +234,27 @@ describe("ApiClient", () => { ).resolves.toMatchObject({ id: "", status: "" }); }); + it("deleteCloudRuntimeNode sends DELETE with JSON body containing node id", async () => { + const fetchMock = vi.fn().mockResolvedValueOnce( + new Response(null, { status: 204 }), + ); + vi.stubGlobal("fetch", fetchMock); + + const client = new ApiClient("https://api.example.test"); + await client.deleteCloudRuntimeNode("node-abc-123"); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, opts] = fetchMock.mock.calls[0]!; + expect(url).toBe("https://api.example.test/api/cloud-runtime/nodes"); + expect(opts).toMatchObject({ + method: "DELETE", + body: JSON.stringify({ id: "node-abc-123" }), + }); + expect((opts.headers as Record)["Content-Type"]).toBe( + "application/json", + ); + }); + describe("getAttachment", () => { it("returns the parsed attachment for a well-formed response", async () => { vi.stubGlobal( diff --git a/packages/core/api/client.ts b/packages/core/api/client.ts index e09dd59da..b04a7139e 100644 --- a/packages/core/api/client.ts +++ b/packages/core/api/client.ts @@ -870,8 +870,10 @@ export class ApiClient { } async deleteCloudRuntimeNode(nodeId: string): Promise { - await this.fetch(`/api/cloud-runtime/nodes/${nodeId}`, { + await this.fetchRaw("/api/cloud-runtime/nodes", { method: "DELETE", + body: JSON.stringify({ id: nodeId }), + extraHeaders: { "Content-Type": "application/json" }, }); }