2026-07-20 09:44:06 -03:00
|
|
|
const test = require('node:test');
|
|
|
|
|
const assert = require('node:assert/strict');
|
|
|
|
|
|
|
|
|
|
// --- Mock das integrações externas (property-access em tempo de chamada => patchável) ---
|
|
|
|
|
const geogridService = require('../src/shared/apis/geogridService.js');
|
|
|
|
|
const googleService = require('../src/shared/apis/googleService.js');
|
|
|
|
|
|
|
|
|
|
// Guarda a implementação real de consultarCaixasProximas (outros testes a substituem por mock).
|
|
|
|
|
const realConsultarCaixasProximas = geogridService.consultarCaixasProximas;
|
|
|
|
|
|
|
|
|
|
// cep-promise é chamado como função default => substitui no cache de módulos
|
|
|
|
|
const cepPath = require.resolve('cep-promise');
|
|
|
|
|
require.cache[cepPath] = {
|
|
|
|
|
id: cepPath, filename: cepPath, loaded: true,
|
|
|
|
|
exports: async () => ({ street: 'Rua Imirim', neighborhood: 'Chácaras Marco', city: 'Barueri', state: 'SP', cep: '06419240' })
|
|
|
|
|
};
|
|
|
|
|
googleService.geocodeWithGoogle = async () => ({ lat: -12.9, lon: -46.9 });
|
|
|
|
|
|
|
|
|
|
const core = require('../src/modules/viabilidade/viabilidade.core.js');
|
|
|
|
|
const service = require('../src/modules/contratacao/contratacao.service.js');
|
|
|
|
|
|
|
|
|
|
function mockGeogrid({ caixas, provedor = 'Sothis', trajeto = 630 } = {}) {
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({
|
|
|
|
|
provedor, tipo: 'autorizado',
|
|
|
|
|
caixas: caixas ?? [{ lat: -12.9, lon: -46.9, distanciaReta: 300, sigla: 'X', provedor: 'Sothis' }]
|
|
|
|
|
});
|
|
|
|
|
geogridService.consultaTrajeto = async () => ({ distancia: trajeto });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('classificarPorDistancia aplica a regra 500/1000', () => {
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(0), { dedicado: true, naoDedicado: true });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(500), { dedicado: true, naoDedicado: true });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(501), { dedicado: true, naoDedicado: false });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(1000), { dedicado: true, naoDedicado: false });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(1001), { dedicado: false, naoDedicado: false });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia('5KM+'), { dedicado: false, naoDedicado: false });
|
|
|
|
|
assert.deepEqual(core.classificarPorDistancia(null), { dedicado: false, naoDedicado: false });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('core usa a distância REAL (trajeto), não a linha reta', async () => {
|
|
|
|
|
mockGeogrid({ caixas: [{ lat: -12.9, lon: -46.9, distanciaReta: 300, sigla: 'X' }], trajeto: 630 });
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: -12.9, lon: -46.9 });
|
|
|
|
|
assert.equal(r.distanciaReta, 300);
|
|
|
|
|
assert.equal(r.distanciaReal, 630);
|
|
|
|
|
assert.equal(r.distancia, 630); // efetiva = real
|
|
|
|
|
assert.equal(r.dedicado, true); // 630 <= 1000
|
|
|
|
|
assert.equal(r.naoDedicado, false); // 630 > 500 (a reta 300 diria true => prova que usa a real)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('core pega a MENOR distância real entre as top-N caixas', async () => {
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({
|
|
|
|
|
provedor: 'Sothis', tipo: 'autorizado',
|
|
|
|
|
caixas: [{ lat: 1, lon: 1, distanciaReta: 100, sigla: 'A' }, { lat: 2, lon: 2, distanciaReta: 200, sigla: 'B' }]
|
|
|
|
|
});
|
|
|
|
|
const trajetos = [800, 450];
|
|
|
|
|
let i = 0;
|
|
|
|
|
geogridService.consultaTrajeto = async () => ({ distancia: trajetos[i++] });
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: 0, lon: 0 });
|
|
|
|
|
assert.equal(r.distanciaReal, 450);
|
|
|
|
|
assert.equal(r.naoDedicado, true); // 450 <= 500
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('core cai para a linha reta se o trajeto falhar', async () => {
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({
|
|
|
|
|
provedor: 'Sothis', tipo: 'autorizado', caixas: [{ lat: 1, lon: 1, distanciaReta: 480, sigla: 'A' }]
|
|
|
|
|
});
|
|
|
|
|
geogridService.consultaTrajeto = async () => { throw new Error('trajeto indisponível'); };
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: 0, lon: 0 });
|
|
|
|
|
assert.equal(r.distanciaReal, null);
|
|
|
|
|
assert.equal(r.distancia, 480); // fallback reta
|
|
|
|
|
assert.equal(r.naoDedicado, true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('sem caixa no raio: mantém "5KM+" e nada viável', async () => {
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({ provedor: 'Nenhum provedor disponível', tipo: 'nenhum', caixas: [] });
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: 0, lon: 0 });
|
|
|
|
|
assert.equal(r.distancia, '5KM+');
|
|
|
|
|
assert.equal(r.dedicado, false);
|
|
|
|
|
assert.equal(r.naoDedicado, false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- CONTRATO WordPress: a resposta precisa manter TODAS as chaves e tipos ---
|
|
|
|
|
const CONTRATO_VIABILIDADE = {
|
|
|
|
|
nome: 'string', email: 'string', telefone: 'string',
|
|
|
|
|
logradouro: 'string', numero: 'string', bairro: 'string',
|
|
|
|
|
cidade: 'string', estado: 'string', cep: 'string',
|
|
|
|
|
naoDedicado: 'boolean', dedicado: 'boolean',
|
|
|
|
|
distancia: 'number', provedor: 'string',
|
|
|
|
|
distanciaReta: 'number' // aditivo, não-quebra
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
test('contrato /viabilidade: chaves e tipos preservados', async () => {
|
|
|
|
|
mockGeogrid({ trajeto: 630 });
|
|
|
|
|
const resp = await service.verificarViabilidade({
|
|
|
|
|
nome: 'João', email: 'j@x.com', telefone: '11999999999',
|
|
|
|
|
cep: '06419240', numero: '303', source: 'teste' // source => não persiste no banco
|
|
|
|
|
});
|
|
|
|
|
for (const [campo, tipo] of Object.entries(CONTRATO_VIABILIDADE)) {
|
|
|
|
|
assert.ok(campo in resp, `campo "${campo}" sumiu do response`);
|
|
|
|
|
assert.equal(typeof resp[campo], tipo, `tipo de "${campo}" mudou`);
|
|
|
|
|
}
|
|
|
|
|
assert.equal(resp.distancia, 630); // distancia = real
|
|
|
|
|
assert.equal(resp.distanciaReta, 300);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('contrato /viabilidade/lat-long: chaves preservadas', async () => {
|
|
|
|
|
mockGeogrid({ trajeto: 630 });
|
|
|
|
|
googleService.reverseGeocodeWithGoogle = async () => 'Av. Teste, 1000 - SP';
|
|
|
|
|
const resp = await service.verificarViabilidadeLatLong('-12.9', '-46.9');
|
|
|
|
|
for (const campo of ['endereco', 'naoDedicado', 'dedicado', 'distancia', 'provedor', 'distanciaReta']) {
|
|
|
|
|
assert.ok(campo in resp, `campo "${campo}" faltando`);
|
|
|
|
|
}
|
|
|
|
|
assert.equal(resp.distancia, 630);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('mapCaixa converte distância/coords string do GeoGrid para número', () => {
|
|
|
|
|
const c = geogridService.mapCaixa({ latitude: '-23.66', longitude: '-46.59', distancia: '395.14', pasta: { sigla: 'X' } });
|
|
|
|
|
assert.equal(typeof c.distanciaReta, 'number');
|
|
|
|
|
assert.equal(c.distanciaReta, 395.14);
|
|
|
|
|
assert.equal(c.lat, -23.66);
|
|
|
|
|
assert.equal(c.lon, -46.59);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('classifica corretamente quando a distância vem como string (regressão)', async () => {
|
|
|
|
|
// caixa com distanciaReta numérica (já mapeada) e trajeto retornando string
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({ provedor: 'Sothis', tipo: 'autorizado', caixas: [{ lat: 1, lon: 1, distanciaReta: 395.14, sigla: 'X' }] });
|
|
|
|
|
geogridService.consultaTrajeto = async () => ({ distancia: '420' }); // string
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: 0, lon: 0 });
|
|
|
|
|
assert.equal(r.distanciaReal, 420); // convertido para número
|
|
|
|
|
assert.equal(r.dedicado, true); // 420 <= 500
|
|
|
|
|
assert.equal(r.naoDedicado, true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('core retorna detalhe por caixa (caixas[]) com provedor por caixa', async () => {
|
|
|
|
|
geogridService.consultarCaixasProximas = async () => ({
|
|
|
|
|
provedor: 'Sothis', tipo: 'misto',
|
|
|
|
|
caixas: [
|
|
|
|
|
{ lat: 1, lon: 1, distanciaReta: 300, sigla: 'A', provedor: 'Sothis' },
|
|
|
|
|
{ lat: 2, lon: 2, distanciaReta: 700, sigla: 'B', provedor: 'Parceiro - X' }
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
const trajetos = [420, 900];
|
|
|
|
|
let i = 0;
|
|
|
|
|
geogridService.consultaTrajeto = async () => ({ distancia: trajetos[i++] });
|
|
|
|
|
const r = await core.avaliarPorCoordenadas({ lat: 0, lon: 0 });
|
|
|
|
|
assert.equal(r.caixas.length, 2);
|
|
|
|
|
assert.equal(r.caixas[0].provedor, 'Sothis');
|
|
|
|
|
assert.equal(r.caixas[0].distancia, 420);
|
|
|
|
|
assert.equal(r.caixas[0].naoDedicado, true); // 420 <= 500
|
|
|
|
|
assert.equal(r.caixas[1].provedor, 'Parceiro - X');
|
|
|
|
|
assert.equal(r.caixas[1].distancia, 900);
|
|
|
|
|
assert.equal(r.caixas[1].dedicado, true); // 900 <= 1000
|
|
|
|
|
assert.equal(r.caixas[1].naoDedicado, false); // 900 > 500
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-21 15:48:28 -03:00
|
|
|
test('classificarSigla: convenção de prefixos s-/p-/sub', () => {
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('s-Barueri'), { tipo: 'autorizado', provedor: 'Sothis' });
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('p-Vivo'), { tipo: 'parceiro', provedor: 'Vivo' });
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('s-sub-Barueri'), { tipo: 'autorizado', provedor: '(Subterrâneo) Sothis' });
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('p-sub-Vivo'), { tipo: 'parceiro', provedor: '(Subterrâneo) Vivo' });
|
2026-07-23 09:57:43 -03:00
|
|
|
// meio de transmissão: aéreo / rádio (marcador logo após o prefixo)
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('S-A-ABC'), { tipo: 'autorizado', provedor: 'Sothis - Aéreo' });
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('P-R-Vivo'), { tipo: 'parceiro', provedor: 'Vivo - Rádio' });
|
|
|
|
|
assert.deepEqual(geogridService.classificarSigla('p-a-Vivo'), { tipo: 'parceiro', provedor: 'Vivo - Aéreo' });
|
2026-07-21 15:48:28 -03:00
|
|
|
assert.equal(geogridService.classificarSigla('Sub Alameda Santos').tipo, 'nenhum');
|
|
|
|
|
assert.equal(geogridService.classificarSigla('São Paulo - SP').tipo, 'nenhum'); // convenção antiga não é mais elegível
|
|
|
|
|
assert.equal(geogridService.classificarSigla('S-SP').provedor, 'Sothis'); // case-insensitive
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('seleção MISTA: só s-/p- (ignora siglas fora da convenção), com subterrâneo', async () => {
|
2026-07-20 09:44:06 -03:00
|
|
|
const axios = require('axios');
|
|
|
|
|
const orig = axios.get;
|
|
|
|
|
axios.get = async () => ({ data: { registros: [
|
|
|
|
|
{ latitude: '0', longitude: '0', distancia: '50', pasta: { sigla: 'Sub Alameda Santos' } }, // NÃO elegível (mais próxima)
|
2026-07-21 15:48:28 -03:00
|
|
|
{ latitude: '1', longitude: '1', distancia: '100', pasta: { sigla: 'p-Vivo' } }, // parceiro
|
|
|
|
|
{ latitude: '2', longitude: '2', distancia: '200', pasta: { sigla: 's-sub-Barueri' } } // Sothis subterrâneo
|
2026-07-20 09:44:06 -03:00
|
|
|
] } });
|
|
|
|
|
try {
|
|
|
|
|
const r = await realConsultarCaixasProximas(0, 0, { topN: 2, misturarProvedores: true });
|
|
|
|
|
assert.equal(r.caixas.length, 2); // sub-alameda excluída
|
2026-07-21 15:48:28 -03:00
|
|
|
assert.equal(r.caixas[0].provedor, 'Vivo');
|
|
|
|
|
assert.equal(r.caixas[1].provedor, '(Subterrâneo) Sothis');
|
2026-07-20 09:44:06 -03:00
|
|
|
} finally {
|
|
|
|
|
axios.get = orig;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('seleção PADRÃO (WordPress): prioriza Sothis e ignora parceiro mais próximo', async () => {
|
|
|
|
|
const axios = require('axios');
|
|
|
|
|
const orig = axios.get;
|
|
|
|
|
axios.get = async () => ({ data: { registros: [
|
2026-07-21 15:48:28 -03:00
|
|
|
{ latitude: '1', longitude: '1', distancia: '100', pasta: { sigla: 'p-Vivo' } },
|
|
|
|
|
{ latitude: '2', longitude: '2', distancia: '200', pasta: { sigla: 's-Barueri' } }
|
2026-07-20 09:44:06 -03:00
|
|
|
] } });
|
|
|
|
|
try {
|
|
|
|
|
const r = await realConsultarCaixasProximas(0, 0, { topN: 2 });
|
|
|
|
|
assert.equal(r.provedor, 'Sothis');
|
2026-07-21 15:48:28 -03:00
|
|
|
assert.equal(r.caixas.length, 1); // só a autorizada (Sothis)
|
2026-07-20 09:44:06 -03:00
|
|
|
assert.equal(r.caixas[0].provedor, 'Sothis');
|
|
|
|
|
} finally {
|
|
|
|
|
axios.get = orig;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('lote: dedup + resultado por item (inclui item inválido) e traz caixas[]', async () => {
|
|
|
|
|
mockGeogrid({ trajeto: 630 });
|
|
|
|
|
const resultados = await service.verificarViabilidadeLote([
|
|
|
|
|
{ latitude: '-12.9', longitude: '-46.9' },
|
|
|
|
|
{ latitude: '-12.9', longitude: '-46.9' }, // duplicado
|
|
|
|
|
{ foo: 'bar' } // inválido
|
|
|
|
|
], {});
|
|
|
|
|
assert.equal(resultados.length, 3);
|
|
|
|
|
assert.equal(resultados[0].distancia, 630);
|
|
|
|
|
assert.ok(Array.isArray(resultados[0].caixas));
|
|
|
|
|
assert.equal(resultados[0].caixas[0].provedor, 'Sothis');
|
|
|
|
|
assert.equal(resultados[1].distancia, 630);
|
|
|
|
|
assert.ok(resultados[2].erro);
|
|
|
|
|
});
|