import assert from "node:assert/strict"; import test from "node:test"; import { FetchClient, type FetchResponse } from "coze-coding-dev-sdk"; import { cozeWebFetchTool } from "../src/tools/web-fetch/index.js"; function resultText(result: { content: { type: string; text?: string }[] }): string { return result.content[0].text ?? ""; } function makeFetchResponse(overrides: Partial = {}): FetchResponse { return { url: "https://example.com", title: "Example Page", content: [ { type: "text", text: "Hello world" }, { type: "link", text: "Link A", url: "https://a.com" }, { type: "image", image: { display_url: "https://img.com/1.png", width: 640, height: 480 } }, ], ...overrides, }; } test("web-fetch: text format renders numbered items with links and images", async (t) => { const resp = makeFetchResponse(); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const result = await cozeWebFetchTool.execute("call-1", { urls: "https://example.com" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("1. Example Page")); assert.ok(text.includes("URL: https://example.com")); assert.ok(text.includes("Hello world")); assert.ok(text.includes("Link A: https://a.com")); assert.ok(text.includes("https://img.com/1.png (640x480)")); }); test("web-fetch: markdown format renders headings and markdown links", async (t) => { const resp = makeFetchResponse(); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const result = await cozeWebFetchTool.execute("call-2", { urls: ["https://example.com"], format: "markdown" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("# Example Page")); assert.ok(text.includes("- [Link A](https://a.com)")); }); test("web-fetch: json format returns valid JSON", async (t) => { const resp = makeFetchResponse(); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const result = await cozeWebFetchTool.execute("call-3", { urls: "https://example.com", format: "json" }, undefined as any, undefined as any, undefined as any); const parsed = JSON.parse(resultText(result)); assert.equal(Array.isArray(parsed), true); assert.equal(parsed[0].url, "https://example.com"); assert.equal(parsed[0].title, "Example Page"); assert.equal(parsed[0].links.length, 1); assert.equal(parsed[0].images.length, 1); }); test("web-fetch: textOnly omits links and images", async (t) => { const resp = makeFetchResponse(); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const result = await cozeWebFetchTool.execute("call-4", { urls: "https://example.com", format: "json", textOnly: true }, undefined as any, undefined as any, undefined as any); const parsed = JSON.parse(resultText(result)); assert.equal(parsed[0].links.length, 0); assert.equal(parsed[0].images.length, 0); }); test("web-fetch: filters out links with missing url", async (t) => { const resp = makeFetchResponse({ content: [ { type: "text", text: "body" }, { type: "link", text: "Valid", url: "https://valid.com" }, { type: "link", text: "Missing URL" }, { type: "link", text: undefined, url: undefined }, ], }); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const textResult = await cozeWebFetchTool.execute("call-5a", { urls: "https://example.com" }, undefined as any, undefined as any, undefined as any); assert.ok(resultText(textResult).includes("Valid: https://valid.com")); assert.ok(!resultText(textResult).includes("Missing URL")); const mdResult = await cozeWebFetchTool.execute("call-5b", { urls: "https://example.com", format: "markdown" }, undefined as any, undefined as any, undefined as any); assert.ok(resultText(mdResult).includes("[Valid](https://valid.com)")); assert.ok(!resultText(mdResult).includes("Missing URL")); }); test("web-fetch: image dimensions only shown when both width and height are numbers", async (t) => { const resp = makeFetchResponse({ content: [ { type: "text", text: "body" }, { type: "image", image: { display_url: "https://img.com/a.png", width: 100, height: 200 } }, { type: "image", image: { display_url: "https://img.com/b.png", width: 100 } }, { type: "image", image: { display_url: "https://img.com/c.png" } }, ], }); t.mock.method(FetchClient.prototype, "fetch", async () => resp); const result = await cozeWebFetchTool.execute("call-6", { urls: "https://example.com" }, undefined as any, undefined as any, undefined as any); const text = resultText(result); assert.ok(text.includes("https://img.com/a.png (100x200)")); assert.ok(text.includes("https://img.com/b.png")); assert.ok(!text.includes("b.png (100x")); assert.ok(text.includes("https://img.com/c.png")); assert.ok(!text.includes("c.png (")); }); test("web-fetch: concurrent fetching preserves order", async (t) => { const callOrder: number[] = []; let callIndex = 0; t.mock.method(FetchClient.prototype, "fetch", async (url: string) => { const idx = callIndex++; const delay = url.includes("slow") ? 50 : 10; await new Promise((r) => setTimeout(r, delay)); callOrder.push(idx); return makeFetchResponse({ url, title: `Page ${idx}` }); }); const urls = ["https://slow.com", "https://fast1.com", "https://fast2.com"]; const result = await cozeWebFetchTool.execute("call-7", { urls, format: "json" }, undefined as any, undefined as any, undefined as any); const parsed = JSON.parse(resultText(result)); assert.equal(parsed.length, 3); assert.equal(parsed[0].url, "https://slow.com"); assert.equal(parsed[1].url, "https://fast1.com"); assert.equal(parsed[2].url, "https://fast2.com"); }); test("web-fetch: returns error content on fetch failure", async (t) => { t.mock.method(FetchClient.prototype, "fetch", async () => { throw new Error("Connection refused"); }); const result = await cozeWebFetchTool.execute("call-8", { urls: "https://example.com" }, undefined as any, undefined as any, undefined as any); assert.ok(resultText(result).includes("Error: Connection refused")); assert.equal((result as any).details.error, true); }); test("web-fetch: single url string is normalized to array", async (t) => { const calls: string[] = []; t.mock.method(FetchClient.prototype, "fetch", async (url: string) => { calls.push(url); return makeFetchResponse({ url }); }); await cozeWebFetchTool.execute("call-9", { urls: "https://single.com" }, undefined as any, undefined as any, undefined as any); assert.deepEqual(calls, ["https://single.com"]); });