Enhance trips API with pagination and filtering support, including validation for parameters

This commit is contained in:
abiandev
2026-07-01 10:41:08 +02:00
parent 9b4ea0b415
commit 73c1416f7a
2 changed files with 273 additions and 32 deletions
+118 -11
View File
@@ -102,7 +102,7 @@ test('GET /api/trips está registrado en /api', () => {
assert.ok(tripsRouteLayer, 'GET /api/trips route is not defined');
});
test('GET /api/trips devuelve viajes del transportista autenticado con aliases legacy', async () => {
test('GET /api/trips devuelve viajes del transportista autenticado con aliases legacy e incluye asignados', async () => {
const mockedTrips = [
{
id_viaje: 84919,
@@ -116,26 +116,36 @@ test('GET /api/trips devuelve viajes del transportista autenticado con aliases l
{
id_viaje: 84918,
cod_viaje: 'VIA-2026-0000',
id_estado: 4,
id_estado: 1,
nombrea: 'Madrid, ES',
nombreb: 'Bilbao, ES',
fecha_salida: '2026-01-21',
fecha_llegada: '2026-01-21'
}
];
let callCount = 0;
db.query = async (sql, params) => {
callCount += 1;
assert.match(sql, /FROM c_viajes_proveedor p/);
assert.match(sql, /INNER JOIN c_viajes v/);
assert.match(sql, /INNER JOIN m_proveedores_trasportistas t/);
assert.match(sql, /id_estado IN \(\?, \?, \?\)/);
assert.match(sql, /id_estado IN \(\?, \?, \?, \?\)/);
if (callCount === 1) {
assert.match(sql, /COUNT\(\*\) AS total/);
assert.deepEqual(params, ['58045340X', 7, 8, 9, 1]);
return [[{ total: 2 }]];
}
assert.match(sql, /AS id_viaje/);
assert.match(sql, /AS id_estado/);
assert.match(sql, /AS nombrea/);
assert.match(sql, /AS nombreb/);
assert.match(sql, /AS fecha_salida/);
assert.match(sql, /AS fecha_llegada/);
assert.deepEqual(params, ['58045340X', 7, 8, 9]);
assert.match(sql, /LIMIT \? OFFSET \?/);
assert.deepEqual(params, ['58045340X', 7, 8, 9, 1, 25, 0]);
return [mockedTrips];
};
@@ -149,13 +159,23 @@ test('GET /api/trips devuelve viajes del transportista autenticado con aliases l
assert.equal(response.statusCode, 200);
assert.deepEqual(response.body, {
trips: mockedTrips
trips: mockedTrips,
page: 1,
limit: 25,
total: 2,
has_more: false
});
assert.equal(response.body.trips[0].id_estado, 7);
assert.equal(response.body.trips[1].id_estado, 1);
});
test('GET /api/trips devuelve lista vacia cuando no hay viajes', async () => {
db.query = async () => [[]];
let callCount = 0;
db.query = async () => {
callCount += 1;
return callCount === 1 ? [[{ total: 0 }]] : [[]];
};
const response = await withServer(async (server) =>
requestJson({
@@ -167,12 +187,16 @@ test('GET /api/trips devuelve lista vacia cuando no hay viajes', async () => {
assert.equal(response.statusCode, 200);
assert.deepEqual(response.body, {
trips: []
trips: [],
page: 1,
limit: 25,
total: 0,
has_more: false
});
});
test('GET /api/trips responde en menos de 1s para 500 viajes mockeados', async () => {
const mockedTrips = Array.from({ length: 500 }, (_, index) => ({
test('GET /api/trips responde en menos de 1s para 25 viajes mockeados', async () => {
const mockedTrips = Array.from({ length: 25 }, (_, index) => ({
id_viaje: 100000 + index,
cod_viaje: `VIA-2026-${String(index + 1).padStart(4, '0')}`,
id_estado: index % 2 === 0 ? 7 : 4,
@@ -182,7 +206,12 @@ test('GET /api/trips responde en menos de 1s para 500 viajes mockeados', async (
fecha_llegada: '2026-01-22 18:11:00'
}));
db.query = async () => [mockedTrips];
let callCount = 0;
db.query = async () => {
callCount += 1;
return callCount === 1 ? [[{ total: 500 }]] : [mockedTrips];
};
const startedAt = Date.now();
const response = await withServer(async (server) =>
@@ -195,10 +224,88 @@ test('GET /api/trips responde en menos de 1s para 500 viajes mockeados', async (
const elapsedMs = Date.now() - startedAt;
assert.equal(response.statusCode, 200);
assert.equal(response.body.trips.length, 500);
assert.equal(response.body.trips.length, 25);
assert.equal(response.body.total, 500);
assert.equal(response.body.has_more, true);
assert.ok(elapsedMs < 1000, `Expected < 1000ms, got ${elapsedMs}ms`);
});
test('GET /api/trips aplica paginacion y filtros en SQL', async () => {
const mockedTrips = [
{
id_viaje: 84919,
cod_viaje: 'VIA-2026-0001',
id_estado: 7,
nombrea: 'Barcelona, ES',
nombreb: 'Lyon, FR',
fecha_salida: '2026-06-30 06:00:00',
fecha_llegada: '2026-06-30 18:11:00'
}
];
let callCount = 0;
db.query = async (sql, params) => {
callCount += 1;
assert.match(sql, /v\.id_estado IN \(\?, \?, \?\)/);
assert.match(sql, /COALESCE\(p\.fecha_salida, v\.fecha_salida\) >= \?/);
assert.match(sql, /COALESCE\(p\.fecha_salida, v\.fecha_salida\) < \?/);
if (callCount === 1) {
assert.match(sql, /COUNT\(\*\) AS total/);
assert.deepEqual(params, ['58045340X', 7, 8, 9, '2026-06-01', '2026-07-01']);
return [[{ total: 26 }]];
}
assert.match(sql, /ORDER BY COALESCE\(p\.fecha_salida, v\.fecha_salida\) DESC, p\.id_viaje DESC/);
assert.match(sql, /LIMIT \? OFFSET \?/);
assert.deepEqual(params, ['58045340X', 7, 8, 9, '2026-06-01', '2026-07-01', 25, 25]);
return [mockedTrips];
};
const response = await withServer(async (server) =>
requestJson({
port: server.address().port,
path: '/api/trips?page=2&limit=25&status_ids=7,8,9&date_from=2026-06-01&date_to=2026-06-30',
authorization: `Bearer ${createToken()}`
})
);
assert.equal(response.statusCode, 200);
assert.equal(response.body.page, 2);
assert.equal(response.body.limit, 25);
assert.equal(response.body.total, 26);
assert.equal(response.body.has_more, false);
assert.deepEqual(response.body.trips, mockedTrips);
});
test('GET /api/trips valida parametros invalidos', async () => {
db.query = async () => {
throw new Error('db.query should not be called with invalid params');
};
const invalidPaths = [
'/api/trips?page=0',
'/api/trips?limit=101',
'/api/trips?status_ids=7,x',
'/api/trips?date_from=2026-02-30',
'/api/trips?date_from=2026-07-01&date_to=2026-06-30'
];
for (const path of invalidPaths) {
const response = await withServer(async (server) =>
requestJson({
port: server.address().port,
path,
authorization: `Bearer ${createToken()}`
})
);
assert.equal(response.statusCode, 400, path);
assert.equal(response.body.success, false);
}
});
test('GET /api/trips devuelve 401 sin token', async () => {
db.query = async () => {
throw new Error('db.query should not be called without token');