// Define siglas autorizadas ANTES de carregar apiConfig (que lê o env no require). process.env.GEOGRID_AUTHORIZED_SIGLAS_PASTAS = 'São Paulo - SP, TMC'; 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 }); test('seleção MISTA: só autorizadas + parceiros (ignora siglas inelegíveis)', async () => { 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) { latitude: '1', longitude: '1', distancia: '100', pasta: { sigla: 'Parceiro - X' } }, // parceiro { latitude: '2', longitude: '2', distancia: '200', pasta: { sigla: 'São Paulo - SP' } } // autorizada => Sothis ] } }); try { const r = await realConsultarCaixasProximas(0, 0, { topN: 2, misturarProvedores: true }); assert.equal(r.caixas.length, 2); // sub-alameda excluída assert.equal(r.caixas[0].provedor, 'Parceiro - X'); assert.equal(r.caixas[1].provedor, 'Sothis'); } 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: [ { latitude: '1', longitude: '1', distancia: '100', pasta: { sigla: 'Parceiro - X' } }, { latitude: '2', longitude: '2', distancia: '200', pasta: { sigla: 'São Paulo - SP' } } ] } }); try { const r = await realConsultarCaixasProximas(0, 0, { topN: 2 }); assert.equal(r.provedor, 'Sothis'); assert.equal(r.caixas.length, 1); // só a autorizada 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); });