|
1 | 1 | import { afterEach, beforeEach, describe, expect, test } from "bun:test"; |
2 | 2 | import { createServer, type Server } from "node:http"; |
3 | 3 | import type { MCPClient } from "../mcp/client.js"; |
4 | | -import { createExaMCPWebFetchTool, runWebFetch, MAX_FETCH_BYTES } from "./web-fetch.js"; |
| 4 | +import { createDynamicToolRunner } from "../tui/dynamic-tool-runner.js"; |
| 5 | +import { |
| 6 | + createExaMCPWebFetchTool, |
| 7 | + createWebFetchTool, |
| 8 | + runWebFetch, |
| 9 | + MAX_FETCH_BYTES, |
| 10 | +} from "./web-fetch.js"; |
5 | 11 |
|
6 | 12 | let server: Server; |
7 | 13 | let baseUrl: string; |
@@ -154,10 +160,57 @@ describe("createExaMCPWebFetchTool", () => { |
154 | 160 | callId: "timeout-call", |
155 | 161 | content: |
156 | 162 | "Error: Request to https://example.com timed out after 1s. Retry with a larger timeout parameter (up to 120s) if the site is slow.", |
157 | | - isError: true, |
158 | 163 | }); |
159 | 164 | }); |
160 | 165 |
|
| 166 | + test("matches the complete native dynamic-runner result for protocol failures", async () => { |
| 167 | + const exa = createTool(async () => "unused"); |
| 168 | + const nativeRunner = createDynamicToolRunner([createWebFetchTool()]); |
| 169 | + const exaRunner = createDynamicToolRunner([exa]); |
| 170 | + const call = { |
| 171 | + id: "protocol-call", |
| 172 | + name: "web_fetch", |
| 173 | + arguments: { url: "ftp://example.com/file" }, |
| 174 | + }; |
| 175 | + |
| 176 | + const nativeResult = await nativeRunner.run(call, new AbortController().signal); |
| 177 | + const exaResult = await exaRunner.run(call, new AbortController().signal); |
| 178 | + |
| 179 | + expect(nativeResult).toEqual({ |
| 180 | + callId: "protocol-call", |
| 181 | + content: 'Error: Unsupported protocol "ftp:"; only http and https are allowed.', |
| 182 | + }); |
| 183 | + expect(exaResult).toEqual(nativeResult); |
| 184 | + }); |
| 185 | + |
| 186 | + test("matches the complete native dynamic-runner result for timeout failures", async () => { |
| 187 | + handler = (_req, _res) => undefined; |
| 188 | + const exa = createTool( |
| 189 | + async (_name, _args, signal) => |
| 190 | + new Promise<string>((_resolve, reject) => { |
| 191 | + signal.addEventListener("abort", () => reject(signal.reason), { once: true }); |
| 192 | + }), |
| 193 | + ); |
| 194 | + const nativeRunner = createDynamicToolRunner([createWebFetchTool()]); |
| 195 | + const exaRunner = createDynamicToolRunner([exa]); |
| 196 | + const call = { |
| 197 | + id: "timeout-call", |
| 198 | + name: "web_fetch", |
| 199 | + arguments: { url: `${baseUrl}/`, timeout: 1 }, |
| 200 | + }; |
| 201 | + |
| 202 | + const [nativeResult, exaResult] = await Promise.all([ |
| 203 | + nativeRunner.run(call, new AbortController().signal), |
| 204 | + exaRunner.run(call, new AbortController().signal), |
| 205 | + ]); |
| 206 | + |
| 207 | + expect(nativeResult).toEqual({ |
| 208 | + callId: "timeout-call", |
| 209 | + content: `Error: Request to ${baseUrl}/ timed out after 1s. Retry with a larger timeout parameter (up to 120s) if the site is slow.`, |
| 210 | + }); |
| 211 | + expect(exaResult).toEqual(nativeResult); |
| 212 | + }); |
| 213 | + |
161 | 214 | test("returns distinct markdown, text, and html representations", async () => { |
162 | 215 | handler = (_req, res) => { |
163 | 216 | res.writeHead(200, { "content-type": "text/html" }); |
|
0 commit comments