git-subtree-dir: relatorio git-subtree-mainline:e322cec92bgit-subtree-split:34bcb132f3
115 lines
4.1 KiB
JavaScript
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');
|
|
});
|