import { describe, it, before, after } from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; import { CyberCoreSigner, AgentNotRunningError, ApplicationNotAuthorizedError, CertificateExpiredError, UserCancelledError, TimeoutError } from '../src/signer-sdk.js'; describe('@cybercore/signer-sdk - Testes de Integração com Mock HTTP Server', () => { let server; let serverPort; let baseUrl; // Servidor Mock HTTP emulando o protocolo CCSP/1 before(async () => { server = http.createServer((req, res) => { // CORS & Preflight res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-CyberCore-AppId'); res.setHeader('Access-Control-Allow-Private-Network', 'true'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { const url = new URL(req.url, `http://127.0.0.1:${serverPort}`); if (url.pathname === '/ccsp/v1/status') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', status: 'ready', agent_version: '0.1.0', os: 'Linux x86_64' })); return; } if (url.pathname === '/ccsp/v1/delay') { // Endpoint propositalmente lento para testar timeout setTimeout(() => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'slow_response' })); }, 300); return; } if (url.pathname === '/ccsp/v1/authorize') { const payload = JSON.parse(body || '{}'); if (payload.application_id === 'blocked-app') { res.writeHead(403, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', status: 'rejected', error_code: 'APPLICATION_NOT_AUTHORIZED', message: 'Aplicação bloqueada pelo usuário.' })); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', status: 'authorized', application_id: payload.application_id })); return; } if (url.pathname === '/ccsp/v1/certificates') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', status: 'success', certificates: [ { id: 'cert-a1-1', alias: 'Certificado A1 Teste', subject_name: 'JOAO DA SILVA:12345678900', certificate_type: 'A1', has_private_key: true } ] })); return; } if (url.pathname === '/ccsp/v1/sign') { const payload = JSON.parse(body || '{}'); if (payload.certificate_hint === 'expired-cert') { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', request_id: payload.request_id, status: 'error', error_code: 'CERTIFICATE_EXPIRED', message: 'Certificado expirou em 2025.' })); return; } if (payload.certificate_hint === 'cancel-cert') { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', request_id: payload.request_id, status: 'error', error_code: 'USER_CANCELLED', message: 'Assinatura cancelada no diálogo.' })); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ version: '1', request_id: payload.request_id, status: 'signed', signature: 'MOCK_SIGNATURE_BASE64', certificate: 'MOCK_CERT_BASE64', algorithm: payload.algorithm || 'SHA256withRSA', document_name: payload.document_name })); return; } res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error_code: 'NOT_FOUND', message: 'Endpoint não encontrado' })); }); }); await new Promise((resolve, reject) => { server.listen(0, '127.0.0.1', () => { serverPort = server.address().port; baseUrl = `http://127.0.0.1:${serverPort}`; resolve(); }); server.on('error', reject); }); }); after(async () => { if (server) { await new Promise(resolve => server.close(resolve)); } }); it('deve confirmar que o agente está ativo com isInstalled()', async () => { const signer = new CyberCoreSigner({ baseUrl }); const installed = await signer.isInstalled(); assert.equal(installed, true); }); it('deve retornar dados de status corretos em getStatus()', async () => { const signer = new CyberCoreSigner({ baseUrl }); const status = await signer.getStatus(); assert.equal(status.version, '1'); assert.equal(status.status, 'ready'); assert.equal(status.agent_version, '0.1.0'); }); it('deve realizar handshake com connect() com sucesso', async () => { const signer = new CyberCoreSigner({ baseUrl, applicationName: 'ERP Teste' }); const connected = await signer.connect(); assert.equal(connected, true); assert.equal(signer.connected, true); }); it('deve lançar ApplicationNotAuthorizedError se o agente rejeitar autorização', async () => { const signer = new CyberCoreSigner({ baseUrl, applicationId: 'blocked-app' }); await assert.rejects( async () => signer.connect(), ApplicationNotAuthorizedError ); }); it('deve listar certificados disponíveis em getCertificates()', async () => { const signer = new CyberCoreSigner({ baseUrl }); const certs = await signer.getCertificates(); assert.ok(Array.isArray(certs)); assert.equal(certs.length, 1); assert.equal(certs[0].id, 'cert-a1-1'); }); it('deve assinar com sucesso via sign()', async () => { const signer = new CyberCoreSigner({ baseUrl }); const result = await signer.sign({ certificateId: 'cert-a1-1', digest: Buffer.alloc(32, 7).toString('base64'), documentName: 'relatorio.pdf' }); assert.equal(result.status, 'signed'); assert.equal(result.signature, 'MOCK_SIGNATURE_BASE64'); assert.equal(result.documentName, 'relatorio.pdf'); }); it('deve tratar erro CERTIFICATE_EXPIRED lançando CertificateExpiredError', async () => { const signer = new CyberCoreSigner({ baseUrl }); await assert.rejects( async () => signer.sign({ certificateId: 'expired-cert', digest: Buffer.alloc(32, 1).toString('base64') }), CertificateExpiredError ); }); it('deve tratar cancelamento de usuário lançando UserCancelledError', async () => { const signer = new CyberCoreSigner({ baseUrl }); await assert.rejects( async () => signer.sign({ certificateId: 'cancel-cert', digest: Buffer.alloc(32, 2).toString('base64') }), UserCancelledError ); }); it('deve lançar TimeoutError quando a chamada exceder o timeoutMs configurado', async () => { const signer = new CyberCoreSigner({ baseUrl, timeoutMs: 50 }); await assert.rejects( async () => signer._fetch('/ccsp/v1/delay'), TimeoutError ); }); it('deve lançar AgentNotRunningError com mensagem acionável se porta estiver inativa', async () => { // Porta inativa aleatória const deadPort = 64999; const signer = new CyberCoreSigner({ host: '127.0.0.1', port: deadPort }); const installed = await signer.isInstalled(300); assert.equal(installed, false); await assert.rejects( async () => signer.getStatus(300), err => { assert.ok(err instanceof AgentNotRunningError); assert.equal(err.code, 'AGENT_NOT_RUNNING'); assert.equal(err.port, deadPort); assert.ok(err.actionableGuidance.includes('bandeja do sistema')); return true; } ); }); });