/* * Exemplo executável — Node.js (>=18). Usa as CLASSES REAIS do SDK `foxnfe` * 1.3.0 e substitui APENAS o transporte HTTP (global fetch) por um mock: * nenhuma chamada de rede, SMTP ou emissão. * * Pré-requisito: npm install https://docs.foxnfe.com.br/downloads/foxnfe-1.3.0.tgz * Rode: node consumer.mjs * Saída de sucesso: NODE_SDK_MOCK_OK ... (exit 0) */ import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import { createClient, Webhook } from 'foxnfe'; // classes reais do SDK // ── 1. Mock só do transporte: intercepta o fetch global ────────────────── // A resposta de /auth/me é aninhada, exatamente como a API entrega: // { user: { …, tenant_id }, tenant: { id, slug, … }, subscription, abilities } const fixtures = { 'auth/me': { user: { id: 1, name: 'Integração', email: 'dev@example.com', tenant_id: 7 }, tenant: { id: 7, slug: 'minha-empresa', name: 'Minha Empresa', status: 'active' }, subscription: null, abilities: [], }, 'webhooks/config': { configured: true, active: true, secret_preview: '••••••••lue-1234' }, }; let externalRequests = 0; const originalFetch = globalThis.fetch; globalThis.fetch = async (url) => { const u = new URL(url); if (u.hostname !== 'sdk.invalid') externalRequests++; // nada deve escapar ao mock const key = u.pathname.replace(/^\/api\/v1\//, ''); if (!(key in fixtures)) throw new Error('rota não simulada: ' + key); return new Response(JSON.stringify(fixtures[key]), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }; try { // baseUrl inerte (sdk.invalid) garante que qualquer chamada real falharia. const client = createClient({ tenantSlug: 'minha-empresa', token: 'token-de-teste-local', baseUrl: 'https://sdk.invalid/api/v1', }); const me = await client.me(); // exercita Client.me() real assert.equal(me.tenant.slug, 'minha-empresa'); // estrutura REAL: me.tenant.slug assert.equal(me.user.tenant_id, 7); // me.user.tenant_id const cfg = await new Webhook(client).getConfig(); // exercita Webhook.getConfig() real assert.equal(cfg.configured, true); assert.ok(!('webhook_secret' in cfg), 'a API nunca devolve o segredo completo'); // ── 2. Verificação de assinatura HMAC sobre o corpo canônico ─────────── // O servidor ordena as chaves recursivamente e não escapa "/" nem unicode. const canonicalize = (v) => Array.isArray(v) ? v.map(canonicalize) : (v && typeof v === 'object') ? Object.keys(v).sort().reduce((a, k) => (a[k] = canonicalize(v[k]), a), {}) : v; const canonicalJson = (p) => JSON.stringify(canonicalize(p)); const verify = (rawBody, header, secret) => { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); const a = Buffer.from(header), b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b); }; const secret = 'whsec_exemplo_apenas_para_teste_local_1234567890'; const envelope = { specversion: '1.0', id: '550e8400-e29b-41d4-a716-446655440000', source: 'https://foxnfe.com.br', type: 'br.com.centralfox.foxnfe.nfe.authorized', datacontenttype: 'application/json', time: '2026-09-05T12:00:00-03:00', data: { nfe_id: 123, status: 'authorized' }, }; const body = Buffer.from(canonicalJson(envelope), 'utf8'); const signature = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex'); const tampered = Buffer.from(canonicalJson({ ...envelope, data: { nfe_id: 999, status: 'authorized' } }), 'utf8'); assert.equal(verify(body, signature, secret), true, 'assinatura válida deveria passar'); assert.equal(verify(tampered, signature, secret), false, 'corpo adulterado deveria falhar'); assert.equal(verify(body, signature, 'segredo-errado'), false, 'segredo errado deveria falhar'); assert.equal(externalRequests, 0, 'nenhuma chamada pode escapar ao mock'); console.log('NODE_SDK_MOCK_OK sdk=foxnfe@1.3.0 config=' + cfg.configured + ' tenant=' + me.tenant.slug + ' hmac_valid=pass hmac_tampered=reject external_requests=0'); } finally { globalThis.fetch = originalFetch; }