const assert = require('node:assert/strict'); const http = require('node:http'); const test = require('node:test'); const jwt = require('jsonwebtoken'); const app = require('../app'); const db = require('../src/config/db'); const TEST_JWT_SECRET = 'test-jwt-secret'; let originalQuery; let originalLegacyMode; let originalJwtSecret; let originalJwtExpiresIn; const withServer = async (callback) => new Promise((resolve, reject) => { const server = app.listen(0, '127.0.0.1'); server.on('error', reject); server.on('listening', async () => { try { const result = await callback(server); server.close((closeError) => { if (closeError) { reject(closeError); return; } resolve(result); }); } catch (error) { server.close(() => reject(error)); } }); }); const postForm = async ({ port, path, form }) => new Promise((resolve, reject) => { const body = new URLSearchParams(form).toString(); const req = http.request( { hostname: '127.0.0.1', port, method: 'POST', path, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body) } }, (res) => { let rawBody = ''; res.on('data', (chunk) => { rawBody += chunk; }); res.on('end', () => { resolve({ statusCode: res.statusCode, body: rawBody ? JSON.parse(rawBody) : null }); }); } ); req.on('error', reject); req.write(body); req.end(); }); test.before(() => { originalQuery = db.query; originalLegacyMode = process.env.LOGIN_LEGACY_MODE; originalJwtSecret = process.env.JWT_SECRET; originalJwtExpiresIn = process.env.JWT_EXPIRES_IN; }); test.after(() => { db.query = originalQuery; process.env.LOGIN_LEGACY_MODE = originalLegacyMode; process.env.JWT_SECRET = originalJwtSecret; process.env.JWT_EXPIRES_IN = originalJwtExpiresIn; }); test('POST /login válido devuelve contrato JWT estándar', async () => { process.env.LOGIN_LEGACY_MODE = '0'; process.env.JWT_SECRET = TEST_JWT_SECRET; process.env.JWT_EXPIRES_IN = '8h'; db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[{ id_transportista: 433, nombre: 'Transportista Demo', id_proveedor: 675, dni: '58045340X', foto_perfil: null, email_operaciones: 'ops@example.com' }]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in login success test: ${sql}`); }; const response = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: '58045340X', contrasena: 'dummy-password' } }) ); assert.equal(response.statusCode, 200); assert.equal(response.body.success, true); assert.equal(typeof response.body.token, 'string'); assert.ok(response.body.token.length > 20); assert.notEqual(response.body, '0'); assert.equal(response.body.user.dni, '58045340X'); assert.equal(response.body.user.id_proveedor, 675); assert.equal(response.body.user.usuario, '58045340X'); assert.equal(response.body.user.email_operaciones, 'ops@example.com'); const verified = jwt.verify(response.body.token, TEST_JWT_SECRET); assert.equal(verified.dni, '58045340X'); assert.equal(verified.id_proveedor, 675); assert.equal(verified.usuario, '58045340X'); }); test('POST /login estándar bloquea con 403 si falta email_operaciones', async () => { process.env.LOGIN_LEGACY_MODE = '0'; process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[{ id_transportista: 433, nombre: 'Transportista Demo', id_proveedor: 675, dni: '58045340X', foto_perfil: null, email_operaciones: null }]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in missing email login test: ${sql}`); }; const response = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: '58045340X', contrasena: 'dummy-password' } }) ); assert.equal(response.statusCode, 403); assert.equal(response.body.success, false); assert.equal(response.body.error, 'Forbidden'); assert.equal(response.body.token, undefined); }); test('POST /login inválido devuelve 401 y no "0" por defecto', async () => { process.env.LOGIN_LEGACY_MODE = '0'; process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in login invalid test: ${sql}`); }; const response = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: 'usuario-invalido', contrasena: 'bad-password' } }) ); assert.equal(response.statusCode, 401); assert.deepEqual(response.body, { success: false, error: 'Invalid credentials' }); }); test('POST /login error interno devuelve 500 estándar', async () => { process.env.LOGIN_LEGACY_MODE = '0'; process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async () => { throw new Error('forced db failure'); }; const response = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: '58045340X', contrasena: 'dummy-password' } }) ); assert.equal(response.statusCode, 500); assert.deepEqual(response.body, { success: false, error: 'Internal server error' }); }); test('POST /login en modo legacy devuelve formato antiguo por flag', async () => { process.env.LOGIN_LEGACY_MODE = '1'; process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[{ id_transportista: 433, nombre: 'Transportista Demo', id_proveedor: 675, dni: '58045340X', foto_perfil: null, email_operaciones: 'ops@example.com' }]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in legacy login test: ${sql}`); }; const successResponse = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: '58045340X', contrasena: 'dummy-password' } }) ); assert.equal(successResponse.statusCode, 200); assert.deepEqual(successResponse.body, ['58045340X', 675]); db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[{ id_transportista: 433, nombre: 'Transportista Demo', id_proveedor: 675, dni: '58045340X', foto_perfil: null, email_operaciones: '' }]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in legacy missing email login test: ${sql}`); }; const missingEmailResponse = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: '58045340X', contrasena: 'dummy-password' } }) ); assert.equal(missingEmailResponse.statusCode, 200); assert.equal(missingEmailResponse.body, '0'); db.query = async (sql) => { if (sql.startsWith('SELECT')) { return [[]]; } if (sql.startsWith('INSERT INTO c_log_app')) { return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL in legacy invalid login test: ${sql}`); }; const invalidResponse = await withServer(async (server) => postForm({ port: server.address().port, path: '/login', form: { usuario: 'usuario-invalido', contrasena: 'bad-password' } }) ); assert.equal(invalidResponse.statusCode, 200); assert.equal(invalidResponse.body, '0'); });