feat: incorporar eCMR y mercancias desde 7fbfd30

Traslada los cambios de 7fbfd300b4ea17212c04528145d366a3f00b91e5 conservando la configuracion de entorno de produccion.
This commit is contained in:
2026-09-29 13:32:12 +02:00
parent fd3a63809b
commit dc545c9d86
10 changed files with 1110 additions and 2 deletions
+112
View File
@@ -0,0 +1,112 @@
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/);
});