Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/gti-scheduling-mcp/test/tools.test.ts
T
GustavoandClaude Sonnet 5 257afe98c0 Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois)
- Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real
- Documentacao: specs, plano, runbook de problemas reais e export do workflow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:23:10 -03:00

101 lines
3.6 KiB
TypeScript

/**
* 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<Response> => {
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<Response> =>
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);
});
});