const test = require('node:test'); const assert = require('node:assert/strict'); const express = require('express'); const jwt = require('jsonwebtoken'); const db = require('../src/config/db'); const writer = require('../src/services/tripCargoWriter'); const routes = require('../src/routes/tripsRoutes'); const originalQuery = db.query; const originalSave = writer.save; process.env.JWT_SECRET = 'cargo-test-secret'; let server; let base; const valid = { id_tipo_embalaje: 1, cantidad: 2, peso: 400, largo: 120, ancho: 80, alto: 150, remontable: true }; test.before(async () => { const app = express(); app.use(express.json()); app.use('/api', routes); await new Promise(resolve => { server = app.listen(0, '127.0.0.1', resolve); }); base = `http://127.0.0.1:${server.address().port}/api/trips`; }); test.afterEach(() => { db.query = originalQuery; writer.save = originalSave; }); test.after(async () => { await new Promise(resolve => server.close(resolve)); }); const request = async (method = 'GET', suffix = '/123/cargo', body, identity = { dni: '12345678A' }) => { const headers = { 'Content-Type': 'application/json' }; if (identity) headers.Authorization = `Bearer ${jwt.sign(identity, process.env.JWT_SECRET)}`; const response = await fetch(base + suffix, { method, headers, body: method === 'GET' || body === undefined ? undefined : JSON.stringify(body) }); return { status: response.status, body: await response.json() }; }; for (const method of ['GET', 'POST', 'PUT']) { for (const identity of [null, { id: 1 }]) { test(`${method} requires authenticated driver ${JSON.stringify(identity)}`, async () => { db.query = writer.save = async () => assert.fail('Must not access data'); assert.equal((await request(method, method === 'PUT' ? '/123/cargo/7' : '/123/cargo', valid, identity)).status, 401); }); } } for (const id of ['0', '-1', '123abc', '2147483648']) { test(`invalid trip ${id}`, async () => { writer.save = async () => assert.fail('Must not save'); assert.equal((await request('POST', `/${id}/cargo`, valid)).status, 404); }); } test('unknown trip and unauthorized driver cannot read cargo', async () => { db.query = async () => [[]]; assert.equal((await request()).status, 404); let calls = 0; db.query = async (sql, params) => { calls++; if (calls === 1) return [[{ id_viaje: 123 }]]; assert.deepEqual(params, [123, '12345678A']); return [[]]; }; assert.equal((await request()).status, 403); assert.equal(calls, 2); }); test('reads cargo, active packaging and document status', async () => { let calls = 0; db.query = async (sql, params) => { calls++; if (calls === 1) return [[{ id_viaje: 123, observaciones_mercancia: 'Material' }]]; if (calls === 2) return [[{ n_proveedor: 1 }]]; if (calls === 3) { assert.deepEqual(params, [123]); return [[{ id_mercancia: 7, peso: '400' }]]; } if (calls === 4) { assert.match(sql, /activo = 1/); return [[{ id_tipo_embalaje: 1 }]]; } assert.deepEqual(params, [123]); return [[{ id_documento: 8 }]]; }; const response = await request(); assert.equal(response.status, 200); assert.equal(response.body.data.ecmr_emitido, true); assert.equal(response.body.data.mercancias[0].id_mercancia, 7); assert.equal(response.body.data.observaciones_mercancia, 'Material'); }); for (const [field, value] of [ ['cantidad', 0], ['cantidad', 1.5], ['cantidad', '2'], ['cantidad', null], ['peso', -1], ['peso', 1.5], ['peso', 10000000000], ['peso', null], ['largo', -10], ['ancho', 2147483648], ['alto', 0.5], ['remontable', 'true'], ['remontable', 2], ['id_tipo_embalaje', 0] ]) { test(`rejects invalid ${field}=${value}`, async () => { writer.save = async () => assert.fail('Must not save invalid cargo'); const response = await request('POST', '/123/cargo', { ...valid, [field]: value }); assert.equal(response.status, 422); assert.equal(response.body.field, field); }); } for (const editing of [false, true]) { test(`saves ${editing ? 'edited' : 'new'} cargo using server identity and new eCMR versions`, async () => { writer.save = async payload => { assert.equal(payload.dni, '12345678A'); assert.equal(payload.trip_id, 123); assert.equal(payload.cargo_id, editing ? 7 : null); assert.equal(payload.mercancia.remontable, 1); assert.equal(payload.database, process.env.DB_NAME); return { status: editing ? 200 : 201, body: { success: true, data: { id_mercancia: 7 }, ecmr_versiones: [{ id_documento: 9, version: 2 }] } }; }; const response = await request(editing ? 'PUT' : 'POST', editing ? '/123/cargo/7' : '/123/cargo', { ...valid, dni: 'forged', trip_id: 999 }); assert.equal(response.status, editing ? 200 : 201); assert.equal(response.body.ecmr_versiones[0].version, 2); }); } for (const status of [403, 404, 422, 500]) { test(`propagates transaction failure ${status}`, async () => { writer.save = async () => ({ status, body: { success: false, error: 'Failed' } }); const response = await request('POST', '/123/cargo', valid); assert.equal(response.status, status); assert.equal(response.body.success, false); }); } test('process failures return 500 without exposing internals', async () => { writer.save = async () => { throw new Error('/private/internal'); }; const response = await request('POST', '/123/cargo', valid); assert.equal(response.status, 500); assert.doesNotMatch(response.body.error, /private/); });