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>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* test/config.test.ts
|
||||
*
|
||||
* Testes unitários para a validação da configuração carregada por
|
||||
* variáveis de ambiente (src/config/env.config.ts).
|
||||
*/
|
||||
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { loadSchedulingConfig } from "../src/config/env.config.js";
|
||||
|
||||
function env(
|
||||
overrides: Record<string, string | undefined> = {},
|
||||
): NodeJS.ProcessEnv {
|
||||
return {
|
||||
SCHEDULING_PROVIDER: "zpro",
|
||||
ZPRO_BASE_URL: "https://zpro.example.com",
|
||||
ZPRO_API_TOKEN: "token-secreto",
|
||||
ZPRO_API_ID: "api-1",
|
||||
ZPRO_INSTANCE_ID: "",
|
||||
...overrides,
|
||||
} as NodeJS.ProcessEnv;
|
||||
}
|
||||
|
||||
describe("loadSchedulingConfig", () => {
|
||||
it("carrega uma configuração válida do Z-PRO", () => {
|
||||
const config = loadSchedulingConfig(
|
||||
env({ ZPRO_INSTANCE_ID: "instancia-1" }),
|
||||
);
|
||||
|
||||
assert.deepEqual(config, {
|
||||
provider: "zpro",
|
||||
baseUrl: "https://zpro.example.com",
|
||||
apiToken: "token-secreto",
|
||||
apiId: "api-1",
|
||||
instanceId: "instancia-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("define o provider como 'zpro' por padrão", () => {
|
||||
const withoutProvider = env();
|
||||
delete withoutProvider.SCHEDULING_PROVIDER;
|
||||
|
||||
const config = loadSchedulingConfig(withoutProvider);
|
||||
|
||||
assert.equal(config.provider, "zpro");
|
||||
});
|
||||
|
||||
it("trata ZPRO_INSTANCE_ID vazio como ausente", () => {
|
||||
const config = loadSchedulingConfig(env());
|
||||
|
||||
assert.equal(config.instanceId, undefined);
|
||||
});
|
||||
|
||||
it("falha quando ZPRO_BASE_URL está ausente", () => {
|
||||
const missingBaseUrl = env();
|
||||
delete missingBaseUrl.ZPRO_BASE_URL;
|
||||
|
||||
assert.throws(
|
||||
() => loadSchedulingConfig(missingBaseUrl),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /ZPRO_BASE_URL/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("falha quando ZPRO_API_TOKEN está ausente", () => {
|
||||
const missingToken = env();
|
||||
delete missingToken.ZPRO_API_TOKEN;
|
||||
|
||||
assert.throws(
|
||||
() => loadSchedulingConfig(missingToken),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /ZPRO_API_TOKEN/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("falha quando ZPRO_API_ID está ausente", () => {
|
||||
const missingApiId = env();
|
||||
delete missingApiId.ZPRO_API_ID;
|
||||
|
||||
assert.throws(
|
||||
() => loadSchedulingConfig(missingApiId),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /ZPRO_API_ID/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("não expõe o token em mensagens de erro", () => {
|
||||
const missingBaseUrl = env();
|
||||
delete missingBaseUrl.ZPRO_BASE_URL;
|
||||
|
||||
assert.throws(
|
||||
() => loadSchedulingConfig(missingBaseUrl),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.ok(!error.message.includes("token-secreto"));
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("rejeita um provider desconhecido", () => {
|
||||
assert.throws(
|
||||
() => loadSchedulingConfig(env({ SCHEDULING_PROVIDER: "syslife" })),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /SCHEDULING_PROVIDER/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("não exige variáveis ZPRO_* quando o provider é 'klingo'", () => {
|
||||
const config = loadSchedulingConfig({
|
||||
SCHEDULING_PROVIDER: "klingo",
|
||||
KLINGO_BASE_URL: "https://klingo.example.com",
|
||||
KLINGO_APP_TOKEN: "app-token-secreto",
|
||||
} as NodeJS.ProcessEnv);
|
||||
|
||||
assert.deepEqual(config, {
|
||||
provider: "klingo",
|
||||
baseUrl: "https://klingo.example.com",
|
||||
appToken: "app-token-secreto",
|
||||
});
|
||||
});
|
||||
|
||||
it("falha quando KLINGO_BASE_URL está ausente com provider 'klingo'", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
loadSchedulingConfig({
|
||||
SCHEDULING_PROVIDER: "klingo",
|
||||
KLINGO_APP_TOKEN: "app-token-secreto",
|
||||
} as NodeJS.ProcessEnv),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /KLINGO_BASE_URL/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("falha quando KLINGO_APP_TOKEN está ausente com provider 'klingo'", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
loadSchedulingConfig({
|
||||
SCHEDULING_PROVIDER: "klingo",
|
||||
KLINGO_BASE_URL: "https://klingo.example.com",
|
||||
} as NodeJS.ProcessEnv),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /KLINGO_APP_TOKEN/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("não exige variáveis KLINGO_* quando o provider é 'zpro'", () => {
|
||||
const config = loadSchedulingConfig(env());
|
||||
|
||||
assert.equal(config.provider, "zpro");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user