Files
cybercore-signer/sdk/test/mock-server.test.js

257 lines
8.4 KiB
JavaScript

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;
}
);
});
});