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 originalJwtSecret; const createToken = (payload = {}) => jwt.sign( { id: 1, dni: '58045340X', id_proveedor: 675, ...payload }, TEST_JWT_SECRET, { expiresIn: '1h' } ); 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 requestJson = async ({ port, method, path, authorization, body }) => new Promise((resolve, reject) => { const rawBody = body === undefined ? null : JSON.stringify(body); const headers = {}; if (authorization) { headers.authorization = authorization; } if (rawBody !== null) { headers['Content-Type'] = 'application/json'; headers['Content-Length'] = Buffer.byteLength(rawBody); } const req = http.request( { hostname: '127.0.0.1', port, method, path, headers }, (res) => { let responseBody = ''; res.on('data', (chunk) => { responseBody += chunk; }); res.on('end', () => { resolve({ statusCode: res.statusCode, body: responseBody ? JSON.parse(responseBody) : null }); }); } ); req.on('error', reject); if (rawBody !== null) { req.write(rawBody); } req.end(); }); test.before(() => { originalQuery = db.query; originalJwtSecret = process.env.JWT_SECRET; }); test.after(() => { db.query = originalQuery; process.env.JWT_SECRET = originalJwtSecret; }); test.afterEach(() => { db.query = originalQuery; }); test('GET /api/availability devuelve available false si no hay fila', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql, params) => { assert.match(sql, /COUNT\(\*\) AS total/); assert.match(sql, /FROM c_trazabilidad_online/); assert.deepEqual(params, ['58045340X']); return [[{ total: 0 }]]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'GET', path: '/api/availability', authorization: `Bearer ${createToken()}` }) ); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, available: false }); }); test('GET /api/availability devuelve available true si hay fila', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql, params) => { assert.match(sql, /COUNT\(\*\) AS total/); assert.match(sql, /FROM c_trazabilidad_online/); assert.deepEqual(params, ['58045340X']); return [[{ total: 1 }]]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'GET', path: '/api/availability', authorization: `Bearer ${createToken()}` }) ); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, available: true }); }); test('POST /api/availability hace INSERT si no existe', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; let step = 0; db.query = async (sql, params) => { step += 1; if (step === 1) { assert.match(sql, /SELECT id_usuario/); assert.match(sql, /FROM c_trazabilidad_online/); assert.deepEqual(params, ['58045340X']); return [[]]; } assert.match(sql, /INSERT INTO c_trazabilidad_online/); assert.deepEqual(params, ['40.416775', '-3.70379', '58045340X']); return [{ affectedRows: 1 }]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'POST', path: '/api/availability', authorization: `Bearer ${createToken()}`, body: { latitud: 40.416775, longitud: -3.70379, usuario: 'OTHER' } }) ); assert.equal(step, 2); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, available: true }); }); test('POST /api/availability hace UPDATE si existe', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; let step = 0; db.query = async (sql, params) => { step += 1; if (step === 1) { assert.match(sql, /SELECT id_usuario/); assert.deepEqual(params, ['58045340X']); return [[{ id_usuario: '58045340X' }]]; } assert.match(sql, /UPDATE c_trazabilidad_online/); assert.deepEqual(params, ['40.416775', '-3.70379', '58045340X']); return [{ affectedRows: 1 }]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'POST', path: '/api/availability', authorization: `Bearer ${createToken()}`, body: { latitude: 40.416775, longitude: -3.70379 } }) ); assert.equal(step, 2); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, available: true }); }); test('DELETE /api/availability borra la fila', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async (sql, params) => { assert.match(sql, /DELETE FROM c_trazabilidad_online/); assert.deepEqual(params, ['58045340X']); return [{ affectedRows: 1 }]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'DELETE', path: '/api/availability', authorization: `Bearer ${createToken()}` }) ); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, available: false }); }); test('POST /api/locations con availability_mode true actualiza disponibilidad online', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; let step = 0; db.query = async (sql, params) => { step += 1; if (step === 1) { assert.match(sql, /INSERT INTO c_trazabilidad_transportista/); assert.equal(params[0].length, 1); assert.deepEqual(params[0][0].slice(0, 3), ['40.416775', '-3.70379', '58045340X']); return [{ affectedRows: 1 }]; } if (step === 2) { assert.match(sql, /SELECT id_usuario/); assert.match(sql, /FROM c_trazabilidad_online/); assert.deepEqual(params, ['58045340X']); return [[{ id_usuario: '58045340X' }]]; } assert.match(sql, /UPDATE c_trazabilidad_online/); assert.deepEqual(params, ['40.416775', '-3.70379', '58045340X']); return [{ affectedRows: 1 }]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'POST', path: '/api/locations', authorization: `Bearer ${createToken()}`, body: { location: [ { coords: { latitude: 40.416775, longitude: -3.70379 }, params: { availability_mode: 'true' }, timestamp: '2026-06-01T13:20:00Z' } ] } }) ); assert.equal(step, 3); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, count: 1, message: 'Locations saved' }); }); test('POST /api/locations sin availability_mode no toca disponibilidad online', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; let calls = 0; db.query = async (sql, params) => { calls += 1; assert.match(sql, /INSERT INTO c_trazabilidad_transportista/); assert.deepEqual(params[0][0].slice(0, 3), ['40.416775', '-3.70379', '58045340X']); return [{ affectedRows: 1 }]; }; const response = await withServer((server) => requestJson({ port: server.address().port, method: 'POST', path: '/api/locations', authorization: `Bearer ${createToken()}`, body: { latitude: 40.416775, longitude: -3.70379, timestamp: '2026-06-01T13:20:00Z' } }) ); assert.equal(calls, 1); assert.equal(response.statusCode, 200); assert.deepEqual(response.body, { success: true, count: 1, message: 'Locations saved' }); }); test('todas las rutas nuevas requieren JWT valido', async () => { process.env.JWT_SECRET = TEST_JWT_SECRET; db.query = async () => { throw new Error('db.query should not be called without token'); }; const responses = await withServer(async (server) => { const port = server.address().port; return Promise.all([ requestJson({ port, method: 'GET', path: '/api/availability' }), requestJson({ port, method: 'POST', path: '/api/availability', body: { latitude: 1, longitude: 2 } }), requestJson({ port, method: 'DELETE', path: '/api/availability' }) ]); }); assert.deepEqual( responses.map((response) => response.statusCode), [401, 401, 401] ); });