Files
Gustavo Santos d21c4596e1 chore: import relatorio (subtree)
git-subtree-dir: relatorio
git-subtree-mainline: e322cec92b
git-subtree-split: 34bcb132f3
2026-10-01 14:26:02 -03:00

115 lines
4.1 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const {
redact,
truncate,
buildTicketContext,
buildJsonSchema,
parseAiResponse,
} = require('../src/v2/aiSchema');
const { loadConfig } = require('../src/v2/config');
const config = loadConfig();
test('redact remove padroes comuns de segredo antes de mandar para a IA', () => {
const texto = 'acesso com senha: abc123 e token=xyz999';
const limpo = redact(texto);
assert.doesNotMatch(limpo, /abc123/);
assert.doesNotMatch(limpo, /xyz999/);
assert.match(limpo, /\[REDACTED\]/);
});
test('truncate corta texto longo e mantem texto curto intacto', () => {
assert.equal(truncate('abc', 10), 'abc');
assert.equal(truncate('a'.repeat(20), 5), 'aaaaa…');
});
test('buildTicketContext respeita AI_MAX_TICKETS', () => {
const cfg = loadConfig({ AI_MAX_TICKETS: 3 });
const tickets = Array.from({ length: 10 }, (_, i) => ({
id: i + 1,
title: `Chamado ${i}`,
priority: 3,
opened_at: '2026-08-20',
}));
const ctx = buildTicketContext(tickets, cfg);
assert.equal(ctx.tickets.length, 3);
assert.equal(ctx.total_disponivel, 10);
assert.equal(ctx.truncado, true);
});
test('buildTicketContext prioriza prioridade mais alta quando precisa cortar', () => {
const cfg = loadConfig({ AI_MAX_TICKETS: 1 });
const tickets = [
{ id: 1, title: 'baixa', priority: 1, opened_at: '2026-08-20' },
{ id: 2, title: 'alta', priority: 5, opened_at: '2026-08-19' },
];
const ctx = buildTicketContext(tickets, cfg);
assert.equal(ctx.tickets[0].id, 2);
});
test('buildTicketContext nunca ultrapassa AI_MAX_CONTEXT_CHARS mesmo com muitos tickets pequenos', () => {
const cfg = loadConfig({ AI_MAX_TICKETS: 1000, AI_MAX_CONTEXT_CHARS: 500 });
const tickets = Array.from({ length: 200 }, (_, i) => ({
id: i + 1,
title: `Chamado numero ${i} com um titulo mediano para ocupar espaco`,
description: 'descricao razoavelmente longa para ajudar a estourar o limite de contexto configurado',
priority: 3,
opened_at: '2026-08-20',
}));
const ctx = buildTicketContext(tickets, cfg);
assert.ok(JSON.stringify(ctx.tickets).length <= cfg.AI_MAX_CONTEXT_CHARS);
assert.ok(ctx.tickets.length < 200);
});
test('buildTicketContext nao envia campos alem do minimo necessario (minimizacao de contexto)', () => {
const ctx = buildTicketContext(
[{ id: 1, title: 'x', priority: 1, opened_at: '2026-08-20', extraCampoSensivel: 'nao deveria ir' }],
config
);
assert.equal(ctx.tickets[0].extraCampoSensivel, undefined);
});
test('buildJsonSchema define os tipos de insight da missao e exige evidencia', () => {
const schema = buildJsonSchema();
assert.equal(schema.strict, true);
assert.ok(schema.schema.properties.insights.items.required.includes('evidence_ticket_ids'));
assert.ok(schema.schema.properties.insights.items.properties.type.enum.includes('CATEGORY_SPIKE'));
});
test('parseAiResponse aceita JSON valido', () => {
const r = parseAiResponse(JSON.stringify({ period_assessment: { status: 'stable', summary: 'ok' }, insights: [] }));
assert.equal(r.ok, true);
assert.equal(r.data.period_assessment.status, 'stable');
});
test('parseAiResponse tolera cercas de markdown ```json ... ```', () => {
const bruto = '```json\n{"period_assessment":{"status":"stable","summary":"ok"},"insights":[]}\n```';
const r = parseAiResponse(bruto);
assert.equal(r.ok, true);
});
test('parseAiResponse nunca lanca excecao com JSON quebrado, so retorna ok:false', () => {
assert.doesNotThrow(() => {
const r = parseAiResponse('{ isso nao é json valido');
assert.equal(r.ok, false);
assert.match(r.error, /json_invalido/);
});
});
test('parseAiResponse rejeita resposta vazia/undefined sem lancar excecao', () => {
assert.doesNotThrow(() => {
assert.equal(parseAiResponse(undefined).ok, false);
assert.equal(parseAiResponse('').ok, false);
assert.equal(parseAiResponse(null).ok, false);
});
});
test('parseAiResponse rejeita objeto sem o formato esperado (falta insights[])', () => {
const r = parseAiResponse(JSON.stringify({ foo: 'bar' }));
assert.equal(r.ok, false);
assert.equal(r.error, 'formato_inesperado');
});