/** * test/tools.test.ts * * Testes unitários para o tratamento de erros das ferramentas MCP. * * Os handlers devem capturar qualquer erro lançado pelo Adapter e devolver * uma resposta MCP estruturada, sem encerrar o servidor e sem vazar * detalhes de implementação (stack trace, nomes de classe internos, * token). Testes de comportamento específico do ZProAdapter (mapeamento * de requisição, classificação de erros HTTP) ficam em * `test/zpro-adapter.test.ts`; aqui testamos apenas a camada da tool. */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { ZProAdapter } from "../src/adapters/zpro/zpro.adapter.js"; import { createConsultarHorariosHandler } from "../src/tools/consultar-horarios.tool.js"; import { createAgendarHandler } from "../src/tools/agendar.tool.js"; import type { ZProConfig } from "../src/config/config.types.js"; const config: ZProConfig = { provider: "zpro", baseUrl: "https://zpro.example.com", apiToken: "token-secreto", apiId: "api-1", }; describe("consultar_horarios", () => { it("retorna resposta de erro estruturada quando o Z-PRO não suporta a consulta", async () => { const adapter = new ZProAdapter(config); const handler = createConsultarHorariosHandler(adapter); const result = await handler({ clinicId: "clinic-1" }); const payload = JSON.parse(result.content[0].text); assert.equal(payload.success, false); assert.equal(payload.error.code, "INTERNAL_ERROR"); }); it("não retorna stack trace nem nomes de classe internos na resposta", async () => { const adapter = new ZProAdapter(config); const handler = createConsultarHorariosHandler(adapter); const result = await handler({ clinicId: "clinic-1" }); assert.ok(!result.content[0].text.includes("at ")); assert.ok(!result.content[0].text.includes("ZProAdapter")); assert.ok(!result.content[0].text.includes("ZProApiError")); }); }); describe("agendar", () => { const failingFetch = async (): Promise => { throw new TypeError("fetch failed"); }; const agendarInput = { clinicId: "clinic-1", startAt: "2026-04-15T14:00:00.000Z", endAt: "2026-04-15T15:00:00.000Z", patient: { name: "Paciente Exemplo" }, }; it("retorna resposta de erro estruturada quando a chamada ao Z-PRO falha", async () => { const adapter = new ZProAdapter(config, { fetchImpl: failingFetch }); const handler = createAgendarHandler(adapter); const result = await handler(agendarInput); const payload = JSON.parse(result.content[0].text); assert.equal(payload.success, false); assert.equal(payload.error.code, "INTERNAL_ERROR"); }); it("não vaza o token do Z-PRO na resposta de erro", async () => { const adapter = new ZProAdapter(config, { fetchImpl: failingFetch }); const handler = createAgendarHandler(adapter); const result = await handler(agendarInput); assert.ok(!result.content[0].text.includes(config.apiToken)); }); it("retorna success:true quando o Z-PRO confirma a criação (HTTP 2xx) — não deve parecer falha", async () => { const acceptingFetch = async (): Promise => new Response(JSON.stringify({ id: "algo" }), { status: 201, headers: { "content-type": "application/json" }, }); const adapter = new ZProAdapter(config, { fetchImpl: acceptingFetch }); const handler = createAgendarHandler(adapter); const result = await handler(agendarInput); const payload = JSON.parse(result.content[0].text); assert.equal(payload.success, true); assert.equal(payload.data.responseMappingPending, true); assert.notEqual(result.isError, true); }); });