Compare commits
3 Commits
5828395907
...
12364bcb44
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12364bcb44 | ||
|
|
6b7c7ec462 | ||
|
|
48b349d68b |
@ -1,11 +1,11 @@
|
|||||||
PORT=3001
|
PORT=3001
|
||||||
DB_HOST=194.164.175.51
|
DB_HOST=localhost
|
||||||
DB_USER=roganet
|
DB_USER=roganet
|
||||||
DB_PASSWORD=bdIRGLnet2905*/
|
DB_PASSWORD=bdIRGLnet2905*/
|
||||||
DB_NAME=abian_app_produccion
|
DB_NAME=abian_app_produccion
|
||||||
JWT_SECRET=9c64f2727d53bfefaaa17a5fda5009ffe93cae904860c659bd18d2d14ad6b467
|
JWT_SECRET=9c64f2727d53bfefaaa17a5fda5009ffe93cae904860c659bd18d2d14ad6b467
|
||||||
|
|
||||||
DB_HOST_P = 194.164.175.51
|
DB_HOST_P = localhost
|
||||||
DB_USER_P = roganet
|
DB_USER_P = roganet
|
||||||
DB_PASSWORD_P = bdIRGLnet2905*/
|
DB_PASSWORD_P = bdIRGLnet2905*/
|
||||||
DB_NAME_P = abian_app_produccion
|
DB_NAME_P = abian_app_produccion
|
||||||
@ -26,7 +26,7 @@ DRIVER_LICENSE_RETENTION_DAYS=365
|
|||||||
|
|
||||||
#Carga de fotos dual
|
#Carga de fotos dual
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=local
|
TRIP_STATUS_PHOTO_STORAGE_MODE=local
|
||||||
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
TRIP_STATUS_SFTP_HOST=localhost
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=IZYj%c0FiIlCc@rI%W0Z
|
TRIP_STATUS_SFTP_PASSWORD=IZYj%c0FiIlCc@rI%W0Z
|
||||||
|
|||||||
@ -96,7 +96,7 @@ Ejemplo de modo temporal dual:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
||||||
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
TRIP_STATUS_SFTP_HOST=localhost
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=********
|
TRIP_STATUS_SFTP_PASSWORD=********
|
||||||
|
|||||||
@ -96,7 +96,7 @@ Ejemplo de modo temporal dual:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
||||||
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
TRIP_STATUS_SFTP_HOST=localhost
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=********
|
TRIP_STATUS_SFTP_PASSWORD=********
|
||||||
|
|||||||
12
app.js
12
app.js
@ -13,6 +13,7 @@ dotenv.config({ path: path.resolve(__dirname, '.env'), override: true });
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3001;
|
||||||
|
const HOST = process.env.HOST || "127.0.0.1";
|
||||||
const uploadsDir = path.resolve(__dirname, 'uploads');
|
const uploadsDir = path.resolve(__dirname, 'uploads');
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
@ -88,6 +89,17 @@ const limiter = rateLimit({
|
|||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
message: "Too many requests from this IP, please try again after 15 minutes"
|
message: "Too many requests from this IP, please try again after 15 minutes"
|
||||||
});
|
});
|
||||||
|
app.get(["/health", "/healthcheck"], (req, res) => {
|
||||||
|
res.set("Cache-Control", "no-store");
|
||||||
|
res.status(200).json({
|
||||||
|
status: "ok",
|
||||||
|
service: "node-gestion-api",
|
||||||
|
pid: process.pid,
|
||||||
|
uptime_seconds: Math.floor(process.uptime()),
|
||||||
|
timestamp_utc: new Date().toISOString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.use(limiter);
|
app.use(limiter);
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
|
|||||||
BIN
docu/PUSH-API example 1.8.pdf
Normal file
BIN
docu/PUSH-API example 1.8.pdf
Normal file
Binary file not shown.
191
docu/cambiar_estado.php
Normal file
191
docu/cambiar_estado.php
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
include("mysql.php");
|
||||||
|
|
||||||
|
$id_viaje = $_POST['id_viaje'];
|
||||||
|
$n_proveedor = $_POST['n_proveedor'];
|
||||||
|
$usuario = $_POST['usuario'];
|
||||||
|
$id_estado = $_POST['id_estado'];
|
||||||
|
$incidencia = $_POST['incidencia'];
|
||||||
|
$latitud = $_POST['latitud'];
|
||||||
|
$longitud = $_POST['longitud'];
|
||||||
|
$nombre = $_POST['nombre'] . '.jpg';
|
||||||
|
|
||||||
|
if ($nombre == "vacio.jpg") {
|
||||||
|
$nombre = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$consulta = "SELECT count(*) from c_cambios_estado where id_viaje='" . $id_viaje . "' and id_estado='" . $id_estado . "' and foto='" . $nombre . "'";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$duplicado = $fila[0];
|
||||||
|
|
||||||
|
if ($duplicado > 0) {
|
||||||
|
echo json_encode("0");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sQuery2 = "UPDATE c_viajes SET id_estado = $id_estado, ind_edi_app = 1 WHERE id_viaje = $id_viaje";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $sQuery2) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
|
||||||
|
$sQuery2 = "INSERT INTO c_cambios_estado (id_viaje, n_proveedor, id_transportista, id_estado, incidencia, latitud, longitud, fecha_y_hora, foto) VALUES ('" . $id_viaje . "','" . $n_proveedor . "','" . $usuario . "','" . $id_estado . "','" . $incidencia . "','" . $latitud . "','" . $longitud . "',NOW(),'" . $nombre . "')";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $sQuery2) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
|
||||||
|
$consulta = "SELECT id_viaje_padre, id_cliente, cod_viaje from c_viajes where id_viaje='" . $id_viaje . "'";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$id_viaje_padre = $fila[0];
|
||||||
|
$id_cliente = $fila[1];
|
||||||
|
$cod_viaje = $fila[2];
|
||||||
|
|
||||||
|
while ($id_viaje_padre > 0) {
|
||||||
|
$consulta = "UPDATE `c_viajes` SET id_estado='" . $id_estado . "', ind_edi_app = 1 where id_viaje='" . $id_viaje_padre . "'";
|
||||||
|
$query_res = mysqli_query($gaSql['link'], $consulta);
|
||||||
|
|
||||||
|
$consulta = "INSERT INTO c_cambios_estado (id_viaje, n_proveedor, id_transportista, id_estado, incidencia, latitud, longitud, fecha_y_hora, foto) VALUES ('" . $id_viaje . "','" . $n_proveedor . "','" . $usuario . "','" . $id_estado . "','" . $incidencia . "','" . $latitud . "','" . $longitud . "',NOW(),'" . $nombre . "')";
|
||||||
|
$query_res = mysqli_query($gaSql['link'], $consulta);
|
||||||
|
|
||||||
|
//vover consultar para ver si se sale del bucle
|
||||||
|
$consulta = "SELECT id_viaje_padre, id_cr from c_viajes where id_viaje='" . $id_viaje_padre . "'";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$id_viaje_padre = $fila[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado = "0";
|
||||||
|
|
||||||
|
$consulta = "SELECT html FROM html_correo WHERE id_mensaje = 34";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$mensaje = $fila[0];
|
||||||
|
|
||||||
|
$consulta = "SELECT user_smtp_admin, user_smtp_admin, pass_smtp_admin, host_smtp, puerto_smtp, date_format(NOW(), '%Y-%m-%d_%H_%i_%s') as ahora, email_operaciones FROM m_cr WHERE id_cr = $cr";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$email_operaciones = $fila[0];
|
||||||
|
$user_smtp = $fila[1];
|
||||||
|
$pass_smtp = $fila[2];
|
||||||
|
$host_smtp = $fila[3];
|
||||||
|
$puerto_smtp = $fila[4];
|
||||||
|
$ahora = $fila[5];
|
||||||
|
$email_operaciones = $fila[6];
|
||||||
|
|
||||||
|
$consulta = "SELECT estado FROM t_viaje_estados WHERE id_estado = $id_estado";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$estado = $fila[0];
|
||||||
|
|
||||||
|
if (strlen($user_smtp) > 4 && strlen($pass_smtp) > 4 && strlen($host_smtp) > 4) {
|
||||||
|
|
||||||
|
$mail = new PHPMailer();
|
||||||
|
$mail->IsSMTP();
|
||||||
|
$mail->From = $user_smtp;
|
||||||
|
$mail->FromName = "Abian Service";
|
||||||
|
|
||||||
|
$html = '<br>Se ha cambiado el estado del viaje ' . $cod_viaje . ' a ' . $estado . ' mediante la APP';
|
||||||
|
|
||||||
|
$mail->AddAddress($email_operaciones);
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->SMTPSecure = 'tls';
|
||||||
|
$mail->Host = $host_smtp;
|
||||||
|
$mail->Port = $puerto_smtp;
|
||||||
|
$mail->Username = $user_smtp;
|
||||||
|
$mail->Password = $pass_smtp;
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
$mail->Subject = "Facturas pendientes Abian Service";
|
||||||
|
$mail->Body = $mensaje . "<br>" . $html;
|
||||||
|
$mail->IsHTML(true);
|
||||||
|
$mail->SMTPDebug = 0;
|
||||||
|
|
||||||
|
if ($mail->Send()) {
|
||||||
|
echo json_encode("0");
|
||||||
|
} else {
|
||||||
|
echo json_encode("Error al enviar el correo");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo json_encode("Error al enviar el correo: fallo con usuario/contraseña");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id_cliente == 532) {
|
||||||
|
$consulta = "SELECT id_tipovehiculo AS matricula FROM c_viajes_proveedor WHERE id_viaje = $id_viaje AND n_proveedor = $n_proveedor";
|
||||||
|
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
||||||
|
$fila = mysqli_fetch_row($rResult2);
|
||||||
|
$matricula = $fila[0];
|
||||||
|
|
||||||
|
$xml = '
|
||||||
|
<S:Envelope
|
||||||
|
xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
|
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<SOAP-ENV:Header/>
|
||||||
|
<S:Body>
|
||||||
|
<ns2:SubmitPosition xmlns="http://schemas.datacontract.org/2004/07/QESE.QFV.Classes.QAP"
|
||||||
|
xmlns:ns2="QESE.QFV.QAP" xmlns:ns3="http://schemas.datacontract.org/2004/07/System"
|
||||||
|
xmlns:ns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
|
||||||
|
xmlns:ns5="http://schemas.datacontract.org/2004/07/QESE.QFV.Classes"
|
||||||
|
xmlns:ns6="http://schemas.datacontract.org/2004/07/FV.Enums"
|
||||||
|
xmlns:ns7="http://schemas.datacontract.org/2004/07/FV.Enums.DriverHours"
|
||||||
|
xmlns:ns8="http://schemas.microsoft.com/2003/10/Serialization/">
|
||||||
|
<ns2:credentials>
|
||||||
|
<Client>abian</Client>
|
||||||
|
<Password>TVwuW57u0^</Password>
|
||||||
|
<UserName>abian-5567</UserName>
|
||||||
|
<Version>1</Version>
|
||||||
|
</ns2:credentials>
|
||||||
|
<ns2:positions>
|
||||||
|
<Position>
|
||||||
|
<AssetCategory>1</AssetCategory>
|
||||||
|
<CID>5567</CID>
|
||||||
|
<CustomerName/>
|
||||||
|
<DT>
|
||||||
|
<ns3:DateTime>?fecha_hora?</ns3:DateTime>
|
||||||
|
<ns3:OffsetMinutes>0</ns3:OffsetMinutes>
|
||||||
|
</DT>
|
||||||
|
<DeviceType>14</DeviceType>
|
||||||
|
<From>' . $matricula . '</From>
|
||||||
|
<GatewayMsgId/>
|
||||||
|
<IdentifierType>12</IdentifierType>
|
||||||
|
<MSISDN>' . $matricula . '</MSISDN>
|
||||||
|
<RequestId>1</RequestId>
|
||||||
|
<Version>1</Version>
|
||||||
|
<Latitude>?latitud?</Latitude>
|
||||||
|
<Longitude>?longitud?</Longitude>
|
||||||
|
</Position>
|
||||||
|
</ns2:positions>
|
||||||
|
</ns2:SubmitPosition>
|
||||||
|
</S:Body>
|
||||||
|
</S:Envelope>';
|
||||||
|
|
||||||
|
$url = 'https://export.fleetvisor.eu/wsQAP/Positions.svc/ssl';
|
||||||
|
$str = date("Y-m-d");
|
||||||
|
$strb = date("H:i:s");
|
||||||
|
|
||||||
|
$fecha_hora = $str . "T" . $strb . "Z";
|
||||||
|
|
||||||
|
$xml = str_replace("?fecha_hora?", $fecha_hora, $xml);
|
||||||
|
$xml = str_replace("?latitud?", $latitud, $xml);
|
||||||
|
$xml = str_replace("?longitud?", $longitud, $xml);
|
||||||
|
|
||||||
|
//echo $xml;
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($curl, CURLOPT_POST, true);
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
"Content-Type: text/xml",
|
||||||
|
"SOAPAction: QESE.QFV.QAP/PositionsService/SubmitPosition"
|
||||||
|
);
|
||||||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
$data = $xml;
|
||||||
|
|
||||||
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||||
|
|
||||||
|
$resp = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($resultado);
|
||||||
|
?>
|
||||||
109
docu/correo.txt
Normal file
109
docu/correo.txt
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Dear Gorka,
|
||||||
|
thanks for your reply!
|
||||||
|
|
||||||
|
We have a standard API that we provide openly - please see the attached documentation for more details.
|
||||||
|
The attached document also includes the URL and apikey for our TEST instance.
|
||||||
|
|
||||||
|
- endpoint URL for test and production
|
||||||
|
|
||||||
|
PROD URL: https://push-dhl.agheera.com/Telematics/positions
|
||||||
|
apiKey for Abian: 2NL7G-0QTMP-T0N54-7ERGY-S7U6U-SXKWF
|
||||||
|
TEST URL: https://push-test.agheera.com/Telematics/Positions
|
||||||
|
apiKey for testing purposes: 0eec610a-aad5-4f60-91c7-7f9a4089d5b5
|
||||||
|
|
||||||
|
- authentication method and credentials process
|
||||||
|
Authentication is done by sending an apikey in a HTTP header "apiKey" with each request.
|
||||||
|
There is no further handshake (oauth flow or similar). If the apikey is correct and accepted, data will be assigned to the customer (in this case Abian)
|
||||||
|
Apikey is provided by Agheera on request.
|
||||||
|
1 Apikey is bound to 1 customer and 1 environment (prod/test).
|
||||||
|
|
||||||
|
- required payload format
|
||||||
|
Please see attached documentation for more details.
|
||||||
|
We encourage to send data batches whenever possible.
|
||||||
|
|
||||||
|
- required vehicle identifier: license plate, device ID, MSISDN, etc.
|
||||||
|
Device ID and License Plate shall both be provided
|
||||||
|
|
||||||
|
- timestamp format/timezone
|
||||||
|
Must be in UTC timezone
|
||||||
|
Format: yyyy-MM-ddTHH:mm:ssZ
|
||||||
|
|
||||||
|
- required fields for position updates
|
||||||
|
Minimum required are: latitude, longitude, vehicleid(deviceid), licensePlate, measurementTime
|
||||||
|
We appreciate the following fields to have better Trip ETA calculation: speed, direction, assetType
|
||||||
|
|
||||||
|
- expected response codes
|
||||||
|
200 - OK
|
||||||
|
If data was accepted and was syntactically correct
|
||||||
|
400 - Bad request
|
||||||
|
If data was syntactically wrong/malformed
|
||||||
|
401 - Unauthorized
|
||||||
|
Wrong apikey or apikey header missing
|
||||||
|
405 - Method not available
|
||||||
|
If request method was not POST
|
||||||
|
|
||||||
|
- retry/error handling recommendations
|
||||||
|
We are not validating the semantics of position data at the point of response generation, so we return 200 - OK on almost all requests unless the request is badly malformed or unauthorized.
|
||||||
|
This also means a retry is not very likely needed, unless the Agheera server is down.
|
||||||
|
For sending position data to us, it is fine to "fire and forget" and not schedule a retry.
|
||||||
|
In case you notice any errors, please contact ops@agheera.com and we will investigate.
|
||||||
|
|
||||||
|
- whether positions should be sent only for active DHL trips/customer id 532 or for all authorized vehicles
|
||||||
|
Agheera does already filter the data so that DHL can only see data that is relevant for DHL trips.
|
||||||
|
So as far as we are concerned, you can send for all vehicles, and we will filter it anyway.
|
||||||
|
But I think this should be decided by ABIAN if they want this.
|
||||||
|
|
||||||
|
- any IP allowlist requirements
|
||||||
|
No IP whitelist in place. We do not need to know your IPs upfront.
|
||||||
|
|
||||||
|
|
||||||
|
Let me know if you have any open questions!
|
||||||
|
|
||||||
|
Kind Regards / Freundliche Grüße
|
||||||
|
Stephan Wahlen
|
||||||
|
Head of IoT Hardware
|
||||||
|
Agheera GmbH – a DHL Group company
|
||||||
|
|
||||||
|
stephan.wahlen@agheera.com
|
||||||
|
+49 2203 29757-23
|
||||||
|
Office: August-Horch-Straße 5, 51149 Köln
|
||||||
|
Warehouse: Kasinostraße 24, 53840 Troisdorf, Deutschland
|
||||||
|
Registered office Cologne; Register court Bonn; HRB 18111
|
||||||
|
VAT ID no. DE 273 231 181
|
||||||
|
Managing Directors: Pierre Lynch, Sven Kefferpütz
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----Ursprüngliche Nachricht-----
|
||||||
|
Von: Gorka Leceta <gleceta@roganet.es>
|
||||||
|
Gesendet: Donnerstag, 28. Mai 2026 11:52
|
||||||
|
An: Operations <ops@agheera.com>; Stephan Wahlen <stephan.wahlen@agheera.com>
|
||||||
|
Betreff: ABIAN SERVICE - Agheera Push API documentation request for GPS position integration
|
||||||
|
|
||||||
|
Hello Stephan / Agheera team,
|
||||||
|
|
||||||
|
We are Roganet, GPS provider for ABIAN SERVICE.
|
||||||
|
|
||||||
|
ABIAN has asked us to integrate active position pushing from their mobile app backend to Agheera for DHL transports.
|
||||||
|
|
||||||
|
Could you please send us the technical documentation for Agheera's push API?
|
||||||
|
|
||||||
|
We need:
|
||||||
|
- endpoint URL for test and production
|
||||||
|
- authentication method and credentials process
|
||||||
|
- required payload format
|
||||||
|
- required vehicle identifier: license plate, device ID, MSISDN, etc.
|
||||||
|
- timestamp format/timezone
|
||||||
|
- required fields for position updates
|
||||||
|
- expected response codes
|
||||||
|
- retry/error handling recommendations
|
||||||
|
- whether positions should be sent only for active DHL trips/customer id
|
||||||
|
532 or for all authorized vehicles
|
||||||
|
- any IP allowlist requirements
|
||||||
|
|
||||||
|
ABIAN registration was submitted on 2024-01-23 and the authorization document is AFTemplateV2-ABIAN SERVICE_2024-01-23T11_55_17Z.PDF.
|
||||||
|
|
||||||
|
Kind regards
|
||||||
|
|
||||||
@ -1,4 +1,7 @@
|
|||||||
const db = require('../config/db');
|
const db = require('../config/db');
|
||||||
|
const agheeraPushClient = require('../services/agheeraPushClient');
|
||||||
|
|
||||||
|
const AGHEERA_CLIENT_ID = 532;
|
||||||
|
|
||||||
const getDniFromLocation = (locationData) => {
|
const getDniFromLocation = (locationData) => {
|
||||||
if (locationData?.extras?.alias) {
|
if (locationData?.extras?.alias) {
|
||||||
@ -70,6 +73,137 @@ const getTripIdFromLocation = (locationData) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRawTimestampFromLocation = (locationData) => {
|
||||||
|
const candidates = [
|
||||||
|
locationData?.timestamp,
|
||||||
|
locationData?.location?.timestamp
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const value of candidates) {
|
||||||
|
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||||
|
return String(value).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersistedTimestampFromLocation = (locationData) => {
|
||||||
|
const rawTimestamp = getRawTimestampFromLocation(locationData);
|
||||||
|
|
||||||
|
if (rawTimestamp === null) {
|
||||||
|
return {
|
||||||
|
value: new Date(),
|
||||||
|
usedFallback: true,
|
||||||
|
reason: 'missing'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedTimestamp = new Date(rawTimestamp);
|
||||||
|
if (Number.isNaN(parsedTimestamp.getTime())) {
|
||||||
|
return {
|
||||||
|
value: new Date(),
|
||||||
|
usedFallback: true,
|
||||||
|
reason: 'invalid',
|
||||||
|
rawTimestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: parsedTimestamp,
|
||||||
|
usedFallback: false,
|
||||||
|
reason: null,
|
||||||
|
rawTimestamp
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const pushLocationToAgheera = async ({ latitude, longitude, dni, tripId, measurementTime }) => {
|
||||||
|
if (!tripId || !dni) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tripRows] = await db.query(
|
||||||
|
`SELECT id_cliente
|
||||||
|
FROM c_viajes
|
||||||
|
WHERE id_viaje = ?
|
||||||
|
LIMIT 1`,
|
||||||
|
[tripId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Number.parseInt(tripRows[0]?.id_cliente, 10) !== AGHEERA_CLIENT_ID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseResult = {
|
||||||
|
trip_id: tripId,
|
||||||
|
attempted: true,
|
||||||
|
success: false,
|
||||||
|
http_status: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const [authorizationRows] = await db.query(
|
||||||
|
`SELECT id_tipovehiculo AS matricula
|
||||||
|
FROM c_viajes_proveedor
|
||||||
|
WHERE id_viaje = ?
|
||||||
|
AND dni = ?
|
||||||
|
ORDER BY n_proveedor ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
[tripId, dni]
|
||||||
|
);
|
||||||
|
|
||||||
|
const licensePlate = String(authorizationRows[0]?.matricula || '').trim();
|
||||||
|
if (!licensePlate) {
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
error: 'LICENSE_PLATE_NOT_FOUND'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await agheeraPushClient.pushPosition({
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
vehicleId: licensePlate,
|
||||||
|
licensePlate,
|
||||||
|
measurementTime
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
success: true,
|
||||||
|
http_status: result?.status ?? null
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Agheera push failed after location update:', {
|
||||||
|
tripId,
|
||||||
|
dni,
|
||||||
|
message: error.message,
|
||||||
|
status: error.status || null
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
http_status: error.status || null,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushLocationsToAgheera = async (locationsToPush) => {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const location of locationsToPush) {
|
||||||
|
const result = await pushLocationToAgheera(location);
|
||||||
|
if (result) {
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
const saveLocation = async (req, res) => {
|
const saveLocation = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
@ -100,8 +234,8 @@ const saveLocation = async (req, res) => {
|
|||||||
globalTripId = getTripIdFromLocation(data);
|
globalTripId = getTripIdFromLocation(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const rowsToInsert = [];
|
const rowsToInsert = [];
|
||||||
|
const locationsToPush = [];
|
||||||
|
|
||||||
for (const loc of locations) {
|
for (const loc of locations) {
|
||||||
const coords = getCoordinatesFromLocation(loc);
|
const coords = getCoordinatesFromLocation(loc);
|
||||||
@ -109,13 +243,32 @@ const saveLocation = async (req, res) => {
|
|||||||
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
|
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
|
||||||
|
|
||||||
if (coords && coords.lat !== undefined && coords.lat !== null && coords.lng !== undefined && coords.lng !== null) {
|
if (coords && coords.lat !== undefined && coords.lat !== null && coords.lng !== undefined && coords.lng !== null) {
|
||||||
|
const persistedTimestamp = getPersistedTimestampFromLocation(loc);
|
||||||
|
|
||||||
|
if (persistedTimestamp.usedFallback) {
|
||||||
|
console.warn('Location timestamp fallback applied:', {
|
||||||
|
reason: persistedTimestamp.reason,
|
||||||
|
rawTimestamp: persistedTimestamp.rawTimestamp || null,
|
||||||
|
uuid: loc?.uuid || loc?.location?.uuid || null,
|
||||||
|
tripId,
|
||||||
|
dni: dni || null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rowsToInsert.push([
|
rowsToInsert.push([
|
||||||
String(coords.lat),
|
String(coords.lat),
|
||||||
String(coords.lng),
|
String(coords.lng),
|
||||||
dni || null,
|
dni || null,
|
||||||
now,
|
persistedTimestamp.value,
|
||||||
tripId
|
tripId
|
||||||
]);
|
]);
|
||||||
|
locationsToPush.push({
|
||||||
|
latitude: coords.lat,
|
||||||
|
longitude: coords.lng,
|
||||||
|
dni,
|
||||||
|
tripId,
|
||||||
|
measurementTime: persistedTimestamp.value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,17 +287,24 @@ const saveLocation = async (req, res) => {
|
|||||||
[rowsToInsert]
|
[rowsToInsert]
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.json({
|
const agheeraResults = await pushLocationsToAgheera(locationsToPush);
|
||||||
|
const responseBody = {
|
||||||
success: true,
|
success: true,
|
||||||
count: rowsToInsert.length,
|
count: rowsToInsert.length,
|
||||||
message: 'Locations saved'
|
message: 'Locations saved'
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (agheeraResults.length > 0) {
|
||||||
|
responseBody.agheera_push = agheeraResults.length === 1 ? agheeraResults[0] : agheeraResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(responseBody);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving location:', error);
|
console.error('Error saving location:', error);
|
||||||
return res.status(500).json({ success: false, error: error.message });
|
return res.status(500).json({ success: false, error: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveLocation
|
saveLocation
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const db = require('../config/db');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
|
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
|
||||||
|
const agheeraPushClient = require('../services/agheeraPushClient');
|
||||||
const {
|
const {
|
||||||
collectUploadedTripStatusFiles,
|
collectUploadedTripStatusFiles,
|
||||||
removeUploadedTripStatusFiles,
|
removeUploadedTripStatusFiles,
|
||||||
@ -23,6 +24,7 @@ const INTERMEDIATE_POINT_ALLOWED_STATES = [3, 4, 5];
|
|||||||
const INTERMEDIATE_POINT_ALLOWED_STATES_SET = new Set(INTERMEDIATE_POINT_ALLOWED_STATES);
|
const INTERMEDIATE_POINT_ALLOWED_STATES_SET = new Set(INTERMEDIATE_POINT_ALLOWED_STATES);
|
||||||
const SQL_DATETIME_REGEX = /^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/;
|
const SQL_DATETIME_REGEX = /^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/;
|
||||||
const FAILED_TRIP_STATE = 9;
|
const FAILED_TRIP_STATE = 9;
|
||||||
|
const AGHEERA_CLIENT_ID = 532;
|
||||||
const INTERMEDIATE_POINT_STATUS_IDS = new Set([3, 4, 5]);
|
const INTERMEDIATE_POINT_STATUS_IDS = new Set([3, 4, 5]);
|
||||||
const INCIDENCE_TEXT_CONTROL_CHARACTERS_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
const INCIDENCE_TEXT_CONTROL_CHARACTERS_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
||||||
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
|
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
|
||||||
@ -238,6 +240,85 @@ const normalizeCoordinateValue = (rawValue) => {
|
|||||||
return String(parsedNumericValue);
|
return String(parsedNumericValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pushTripStatusPositionToAgheera = async ({
|
||||||
|
tripRow,
|
||||||
|
authorizationRow,
|
||||||
|
latitud,
|
||||||
|
longitud,
|
||||||
|
measurementTime,
|
||||||
|
tripId,
|
||||||
|
requestId,
|
||||||
|
flow
|
||||||
|
}) => {
|
||||||
|
const clientId = Number.parseInt(tripRow?.id_cliente, 10);
|
||||||
|
if (clientId !== AGHEERA_CLIENT_ID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseResult = {
|
||||||
|
trip_id: tripId,
|
||||||
|
attempted: false,
|
||||||
|
success: false,
|
||||||
|
http_status: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (latitud === null || longitud === null) {
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
error: 'COORDINATES_MISSING'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const licensePlate = String(authorizationRow?.matricula || '').trim();
|
||||||
|
if (!licensePlate) {
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
error: 'LICENSE_PLATE_NOT_FOUND'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await agheeraPushClient.pushPosition({
|
||||||
|
latitude: latitud,
|
||||||
|
longitude: longitud,
|
||||||
|
vehicleId: licensePlate,
|
||||||
|
licensePlate,
|
||||||
|
measurementTime
|
||||||
|
});
|
||||||
|
|
||||||
|
appendTripStatusDebugLog({
|
||||||
|
stage: 'update_trip_status:agheera_push_success',
|
||||||
|
request_id: requestId,
|
||||||
|
flow,
|
||||||
|
trip_id: tripId,
|
||||||
|
http_status: result?.status ?? null
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
attempted: true,
|
||||||
|
success: true,
|
||||||
|
http_status: result?.status ?? null
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Agheera push failed after trip status update:', {
|
||||||
|
tripId,
|
||||||
|
requestId,
|
||||||
|
flow,
|
||||||
|
message: error.message,
|
||||||
|
status: error.status || null
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseResult,
|
||||||
|
attempted: true,
|
||||||
|
http_status: error.status || null,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeSqlDateTimeValue = (rawValue) => {
|
const normalizeSqlDateTimeValue = (rawValue) => {
|
||||||
if (typeof rawValue !== 'string') {
|
if (typeof rawValue !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@ -1420,7 +1501,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
|
|
||||||
const [tripRows] = await connection.query(
|
const [tripRows] = await connection.query(
|
||||||
`SELECT id_viaje, id_estado, id_viaje_padre
|
`SELECT id_viaje, id_estado, id_viaje_padre, id_cliente
|
||||||
FROM c_viajes
|
FROM c_viajes
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -1453,7 +1534,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [authorizationRows] = await connection.query(
|
const [authorizationRows] = await connection.query(
|
||||||
`SELECT n_proveedor, id_proveedor
|
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
||||||
FROM c_viajes_proveedor
|
FROM c_viajes_proveedor
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
AND dni = ?
|
AND dni = ?
|
||||||
@ -1716,8 +1797,18 @@ const updateTripStatus = async (req, res) => {
|
|||||||
idEstadoLogged: FAILED_TRIP_STATE,
|
idEstadoLogged: FAILED_TRIP_STATE,
|
||||||
idPuntoLogged: idPunto
|
idPuntoLogged: idPunto
|
||||||
});
|
});
|
||||||
|
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
||||||
|
tripRow: tripRows[0],
|
||||||
|
authorizationRow: authorizationRows[0],
|
||||||
|
latitud,
|
||||||
|
longitud,
|
||||||
|
measurementTime: now,
|
||||||
|
tripId,
|
||||||
|
requestId,
|
||||||
|
flow: 'failed_branch_manual'
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
const responseBody = {
|
||||||
success: true,
|
success: true,
|
||||||
trip_id: tripId,
|
trip_id: tripId,
|
||||||
updated_status_id: idEstado,
|
updated_status_id: idEstado,
|
||||||
@ -1729,7 +1820,13 @@ const updateTripStatus = async (req, res) => {
|
|||||||
failed_marked: true,
|
failed_marked: true,
|
||||||
fotos_concat: fotosConcat || '',
|
fotos_concat: fotosConcat || '',
|
||||||
updated_at: now.toISOString()
|
updated_at: now.toISOString()
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (agheeraPushResult) {
|
||||||
|
responseBody.agheera_push = agheeraPushResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(responseBody);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
@ -1752,7 +1849,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [tripRows] = await db.query(
|
const [tripRows] = await db.query(
|
||||||
`SELECT id_viaje, id_viaje_padre
|
`SELECT id_viaje, id_viaje_padre, id_cliente
|
||||||
FROM c_viajes
|
FROM c_viajes
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
@ -1783,7 +1880,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [authorizationRows] = await db.query(
|
const [authorizationRows] = await db.query(
|
||||||
`SELECT n_proveedor, id_proveedor
|
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
||||||
FROM c_viajes_proveedor
|
FROM c_viajes_proveedor
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
AND dni = ?
|
AND dni = ?
|
||||||
@ -1912,8 +2009,18 @@ const updateTripStatus = async (req, res) => {
|
|||||||
idEstadoLogged: idEstado,
|
idEstadoLogged: idEstado,
|
||||||
idPuntoLogged: idPunto
|
idPuntoLogged: idPunto
|
||||||
});
|
});
|
||||||
|
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
||||||
|
tripRow: tripRows[0],
|
||||||
|
authorizationRow: authorizationRows[0],
|
||||||
|
latitud,
|
||||||
|
longitud,
|
||||||
|
measurementTime: now,
|
||||||
|
tripId,
|
||||||
|
requestId,
|
||||||
|
flow: 'normal_manual'
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
const responseBody = {
|
||||||
success: true,
|
success: true,
|
||||||
trip_id: tripId,
|
trip_id: tripId,
|
||||||
updated_status_id: idEstado,
|
updated_status_id: idEstado,
|
||||||
@ -1925,7 +2032,13 @@ const updateTripStatus = async (req, res) => {
|
|||||||
failed_marked: false,
|
failed_marked: false,
|
||||||
fotos_concat: fotosConcat || '',
|
fotos_concat: fotosConcat || '',
|
||||||
updated_at: now.toISOString()
|
updated_at: now.toISOString()
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (agheeraPushResult) {
|
||||||
|
responseBody.agheera_push = agheeraPushResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(responseBody);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const parsedTripId = Number.parseInt(req.params?.id, 10);
|
const parsedTripId = Number.parseInt(req.params?.id, 10);
|
||||||
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
|
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
|
||||||
|
|||||||
113
src/services/agheeraPushClient.js
Normal file
113
src/services/agheeraPushClient.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
const DEFAULT_AGHEERA_PUSH_URL = 'https://push-test.agheera.com/Telematics/Positions';
|
||||||
|
|
||||||
|
let httpClientOverride = null;
|
||||||
|
|
||||||
|
const getPushUrl = () =>
|
||||||
|
String(process.env.AGHEERA_PUSH_URL || DEFAULT_AGHEERA_PUSH_URL).trim();
|
||||||
|
|
||||||
|
const getApiKey = () =>
|
||||||
|
String(process.env.AGHEERA_API_KEY || '').trim();
|
||||||
|
|
||||||
|
const formatMeasurementTime = (dateValue) => {
|
||||||
|
const date = dateValue instanceof Date ? dateValue : new Date(dateValue);
|
||||||
|
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeNumber = (rawValue) => {
|
||||||
|
const numericValue = Number(rawValue);
|
||||||
|
return Number.isFinite(numericValue) ? numericValue : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHttpClient = () => {
|
||||||
|
if (typeof httpClientOverride === 'function') {
|
||||||
|
return httpClientOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fetch === 'function') {
|
||||||
|
return fetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildPositionPayload = ({
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
vehicleId,
|
||||||
|
licensePlate,
|
||||||
|
measurementTime
|
||||||
|
}) => ({
|
||||||
|
Vehicles: [
|
||||||
|
{
|
||||||
|
latitude: normalizeNumber(latitude),
|
||||||
|
longitude: normalizeNumber(longitude),
|
||||||
|
vehicleId: String(vehicleId || '').trim(),
|
||||||
|
licensePlate: String(licensePlate || '').trim(),
|
||||||
|
measurementTime: formatMeasurementTime(measurementTime)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const pushPosition = async ({
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
vehicleId,
|
||||||
|
licensePlate,
|
||||||
|
measurementTime
|
||||||
|
}) => {
|
||||||
|
const httpClient = getHttpClient();
|
||||||
|
if (!httpClient) {
|
||||||
|
throw new Error('Agheera HTTP client unavailable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = getApiKey();
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('Agheera API key missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = getPushUrl();
|
||||||
|
const payload = buildPositionPayload({
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
vehicleId,
|
||||||
|
licensePlate,
|
||||||
|
measurementTime
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await httpClient(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
apiKey,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseBody = typeof response?.text === 'function' ? await response.text() : '';
|
||||||
|
if (!response?.ok) {
|
||||||
|
const error = new Error('Agheera push failed');
|
||||||
|
error.status = response?.status || null;
|
||||||
|
error.body = responseBody;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
body: responseBody
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const __setHttpClientForTests = (httpClient) => {
|
||||||
|
httpClientOverride = httpClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
const __resetHttpClientForTests = () => {
|
||||||
|
httpClientOverride = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DEFAULT_AGHEERA_PUSH_URL,
|
||||||
|
pushPosition,
|
||||||
|
__setHttpClientForTests,
|
||||||
|
__resetHttpClientForTests
|
||||||
|
};
|
||||||
289
test/location.agheera.integration.test.js
Normal file
289
test/location.agheera.integration.test.js
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
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 agheeraPushClient = require('../src/services/agheeraPushClient');
|
||||||
|
|
||||||
|
const TEST_JWT_SECRET = 'test-jwt-secret';
|
||||||
|
|
||||||
|
let originalQuery;
|
||||||
|
let originalJwtSecret;
|
||||||
|
let originalAgheeraApiKey;
|
||||||
|
|
||||||
|
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 postJson = async ({ port, path, authorization, body }) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const rawBody = JSON.stringify(body);
|
||||||
|
const req = http.request(
|
||||||
|
{
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port,
|
||||||
|
method: 'POST',
|
||||||
|
path,
|
||||||
|
headers: {
|
||||||
|
authorization,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(rawBody)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(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);
|
||||||
|
req.write(rawBody);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test.before(() => {
|
||||||
|
originalQuery = db.query;
|
||||||
|
originalJwtSecret = process.env.JWT_SECRET;
|
||||||
|
originalAgheeraApiKey = process.env.AGHEERA_API_KEY;
|
||||||
|
});
|
||||||
|
|
||||||
|
test.after(() => {
|
||||||
|
db.query = originalQuery;
|
||||||
|
process.env.JWT_SECRET = originalJwtSecret;
|
||||||
|
process.env.AGHEERA_API_KEY = originalAgheeraApiKey;
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(() => {
|
||||||
|
db.query = originalQuery;
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/locations envia posicion a Agheera para cliente 532', async () => {
|
||||||
|
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
||||||
|
});
|
||||||
|
|
||||||
|
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']);
|
||||||
|
assert.equal(params[0][0][4], 248230);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
assert.match(sql, /FROM c_viajes/);
|
||||||
|
assert.deepEqual(params, [248230]);
|
||||||
|
return [[{ id_cliente: 532 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /FROM c_viajes_proveedor/);
|
||||||
|
assert.deepEqual(params, [248230, '58045340X']);
|
||||||
|
return [[{ matricula: '6599LCN' }]];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
postJson({
|
||||||
|
port: server.address().port,
|
||||||
|
path: '/api/locations',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
latitude: 40.416775,
|
||||||
|
longitude: -3.70379,
|
||||||
|
id_viaje: 248230,
|
||||||
|
timestamp: '2026-06-01T13:20:00Z'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.deepEqual(response.body, {
|
||||||
|
success: true,
|
||||||
|
count: 1,
|
||||||
|
message: 'Locations saved',
|
||||||
|
agheera_push: {
|
||||||
|
trip_id: 248230,
|
||||||
|
attempted: true,
|
||||||
|
success: true,
|
||||||
|
http_status: 200,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(agheeraCalls.length, 1);
|
||||||
|
|
||||||
|
const call = agheeraCalls[0];
|
||||||
|
assert.equal(call.url, 'https://push-test.agheera.com/Telematics/Positions');
|
||||||
|
assert.equal(call.options.headers.apiKey, 'test-api-key');
|
||||||
|
|
||||||
|
const payload = JSON.parse(call.options.body);
|
||||||
|
assert.deepEqual(payload, {
|
||||||
|
Vehicles: [
|
||||||
|
{
|
||||||
|
latitude: 40.416775,
|
||||||
|
longitude: -3.70379,
|
||||||
|
vehicleId: '6599LCN',
|
||||||
|
licensePlate: '6599LCN',
|
||||||
|
measurementTime: '2026-06-01T13:20:00Z'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/locations no envia a Agheera para clientes distintos de 532', async () => {
|
||||||
|
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
||||||
|
});
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql, params) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
assert.match(sql, /INSERT INTO c_trazabilidad_transportista/);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /FROM c_viajes/);
|
||||||
|
assert.deepEqual(params, [248230]);
|
||||||
|
return [[{ id_cliente: 700 }]];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
postJson({
|
||||||
|
port: server.address().port,
|
||||||
|
path: '/api/locations',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
latitude: 40.416775,
|
||||||
|
longitude: -3.70379,
|
||||||
|
id_viaje: 248230,
|
||||||
|
timestamp: '2026-06-01T13:20:00Z'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.deepEqual(response.body, {
|
||||||
|
success: true,
|
||||||
|
count: 1,
|
||||||
|
message: 'Locations saved'
|
||||||
|
});
|
||||||
|
assert.equal(agheeraCalls.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/locations devuelve error de Agheera sin romper guardado local', async () => {
|
||||||
|
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async () => ({
|
||||||
|
ok: false,
|
||||||
|
status: 401,
|
||||||
|
text: async () => 'Unauthorized'
|
||||||
|
}));
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql, params) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
assert.match(sql, /INSERT INTO c_trazabilidad_transportista/);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
assert.match(sql, /FROM c_viajes/);
|
||||||
|
assert.deepEqual(params, [248230]);
|
||||||
|
return [[{ id_cliente: 532 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /FROM c_viajes_proveedor/);
|
||||||
|
assert.deepEqual(params, [248230, '58045340X']);
|
||||||
|
return [[{ matricula: '6599LCN' }]];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
postJson({
|
||||||
|
port: server.address().port,
|
||||||
|
path: '/api/locations',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
latitude: 40.416775,
|
||||||
|
longitude: -3.70379,
|
||||||
|
id_viaje: 248230,
|
||||||
|
timestamp: '2026-06-01T13:20:00Z'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.deepEqual(response.body, {
|
||||||
|
success: true,
|
||||||
|
count: 1,
|
||||||
|
message: 'Locations saved',
|
||||||
|
agheera_push: {
|
||||||
|
trip_id: 248230,
|
||||||
|
attempted: true,
|
||||||
|
success: false,
|
||||||
|
http_status: 401,
|
||||||
|
error: 'Agheera push failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -12,8 +12,11 @@ process.env.TRIP_STATUS_UPLOAD_DIR = TEST_UPLOAD_DIR;
|
|||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual'
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual'
|
||||||
|
|
||||||
const app = require('../app');
|
const app = require('../app');
|
||||||
|
process.env.TRIP_STATUS_UPLOAD_DIR = TEST_UPLOAD_DIR;
|
||||||
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
const db = require('../src/config/db');
|
const db = require('../src/config/db');
|
||||||
const tripStatusPhotoStorage = require('../src/services/tripStatusPhotoStorage');
|
const tripStatusPhotoStorage = require('../src/services/tripStatusPhotoStorage');
|
||||||
|
const agheeraPushClient = require('../src/services/agheeraPushClient');
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
|
const JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
|
||||||
process.env.JWT_SECRET = JWT_SECRET;
|
process.env.JWT_SECRET = JWT_SECRET;
|
||||||
@ -233,6 +236,7 @@ test.after(() => {
|
|||||||
db.query = originalQuery;
|
db.query = originalQuery;
|
||||||
db.getConnection = originalGetConnection;
|
db.getConnection = originalGetConnection;
|
||||||
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
delete process.env.TRIP_STATUS_SFTP_HOST;
|
delete process.env.TRIP_STATUS_SFTP_HOST;
|
||||||
delete process.env.TRIP_STATUS_SFTP_PORT;
|
delete process.env.TRIP_STATUS_SFTP_PORT;
|
||||||
@ -241,6 +245,8 @@ test.after(() => {
|
|||||||
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
||||||
delete process.env.POSTS_LOG_PATH;
|
delete process.env.POSTS_LOG_PATH;
|
||||||
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
||||||
|
delete process.env.AGHEERA_PUSH_URL;
|
||||||
|
delete process.env.AGHEERA_API_KEY;
|
||||||
fs.rmSync(TEST_UPLOAD_DIR, { recursive: true, force: true });
|
fs.rmSync(TEST_UPLOAD_DIR, { recursive: true, force: true });
|
||||||
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
||||||
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
||||||
@ -250,6 +256,7 @@ test.afterEach(() => {
|
|||||||
db.query = originalQuery;
|
db.query = originalQuery;
|
||||||
db.getConnection = originalGetConnection;
|
db.getConnection = originalGetConnection;
|
||||||
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
||||||
|
agheeraPushClient.__resetHttpClientForTests();
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
delete process.env.TRIP_STATUS_SFTP_HOST;
|
delete process.env.TRIP_STATUS_SFTP_HOST;
|
||||||
delete process.env.TRIP_STATUS_SFTP_PORT;
|
delete process.env.TRIP_STATUS_SFTP_PORT;
|
||||||
@ -258,6 +265,8 @@ test.afterEach(() => {
|
|||||||
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
||||||
delete process.env.POSTS_LOG_PATH;
|
delete process.env.POSTS_LOG_PATH;
|
||||||
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
||||||
|
delete process.env.AGHEERA_PUSH_URL;
|
||||||
|
delete process.env.AGHEERA_API_KEY;
|
||||||
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
||||||
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
||||||
});
|
});
|
||||||
@ -710,7 +719,7 @@ test('POST /api/trips/:id/status modo dual replica foto a SFTP y mantiene local'
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -787,7 +796,7 @@ test('POST /api/trips/:id/status modo dual con fallo SFTP mantiene fallback loca
|
|||||||
createFakeSftpClientFactory(recorder, { failPut: true })
|
createFakeSftpClientFactory(recorder, { failPut: true })
|
||||||
);
|
);
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -853,7 +862,7 @@ test('POST /api/trips/:id/status payload inválido tras upload limpia remoto y l
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -1121,6 +1130,269 @@ test('POST /api/trips/:id/status propaga estado global al viaje padre', async ()
|
|||||||
assert.equal(step, 7);
|
assert.equal(step, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('POST /api/trips/:id/status cliente 532 envia posicion a Agheera en estado global', async () => {
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
text: async () => 'Messages received.'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql, params) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
assert.match(sql, /FROM t_viaje_estados/);
|
||||||
|
assert.deepEqual(params, [6]);
|
||||||
|
return [[{ id_estado: 6 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
assert.match(sql, /FROM c_viajes/);
|
||||||
|
assert.match(sql, /id_cliente/);
|
||||||
|
assert.deepEqual(params, [248230]);
|
||||||
|
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 532 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 3) {
|
||||||
|
assert.match(sql, /FROM c_viajes_proveedor/);
|
||||||
|
assert.match(sql, /id_tipovehiculo AS matricula/);
|
||||||
|
assert.deepEqual(params, [248230, '58045340X']);
|
||||||
|
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 4) {
|
||||||
|
assert.match(sql, /UPDATE c_viajes/);
|
||||||
|
assert.deepEqual(params, [6, 1, 248230]);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
||||||
|
assert.equal(params[6], '40.416775');
|
||||||
|
assert.equal(params[7], '-3.70379');
|
||||||
|
return [{ insertId: 6, affectedRows: 1 }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
requestJson({
|
||||||
|
port: server.address().port,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/trips/248230/status',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
id_estado: 6,
|
||||||
|
latitud: '40,416775',
|
||||||
|
longitud: '-3.703790'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.equal(response.body.success, true);
|
||||||
|
assert.deepEqual(response.body.agheera_push, {
|
||||||
|
trip_id: 248230,
|
||||||
|
attempted: true,
|
||||||
|
success: true,
|
||||||
|
http_status: 200,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
assert.equal(agheeraCalls.length, 1);
|
||||||
|
|
||||||
|
const call = agheeraCalls[0];
|
||||||
|
assert.equal(call.url, 'https://push-test.agheera.com/Telematics/Positions');
|
||||||
|
assert.equal(call.options.method, 'POST');
|
||||||
|
assert.equal(call.options.headers.apiKey, 'test-api-key');
|
||||||
|
assert.equal(call.options.headers['Content-Type'], 'application/json');
|
||||||
|
|
||||||
|
const payload = JSON.parse(call.options.body);
|
||||||
|
assert.deepEqual(Object.keys(payload), ['Vehicles']);
|
||||||
|
assert.equal(payload.Vehicles.length, 1);
|
||||||
|
assert.equal(payload.Vehicles[0].latitude, 40.416775);
|
||||||
|
assert.equal(payload.Vehicles[0].longitude, -3.70379);
|
||||||
|
assert.equal(payload.Vehicles[0].vehicleId, '1234ABC');
|
||||||
|
assert.equal(payload.Vehicles[0].licensePlate, '1234ABC');
|
||||||
|
assert.match(payload.Vehicles[0].measurementTime, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/trips/:id/status cliente distinto de 532 no envia a Agheera', async () => {
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
||||||
|
});
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql, params) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
return [[{ id_estado: 6 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 700 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 3) {
|
||||||
|
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 4) {
|
||||||
|
assert.match(sql, /UPDATE c_viajes/);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
||||||
|
return [{ insertId: 6, affectedRows: 1 }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
requestJson({
|
||||||
|
port: server.address().port,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/trips/248230/status',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
id_estado: 6,
|
||||||
|
latitud: '40.416775',
|
||||||
|
longitud: '-3.703790'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.equal(response.body.success, true);
|
||||||
|
assert.equal(response.body.agheera_push, undefined);
|
||||||
|
assert.equal(agheeraCalls.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/trips/:id/status estado intermedio con id_punto no envia a Agheera', async () => {
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
||||||
|
});
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql, params) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
assert.match(sql, /FROM t_viaje_estados/);
|
||||||
|
return [[{ id_estado: 5 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
assert.match(sql, /FROM c_viajes/);
|
||||||
|
return [[{ id_viaje: 248230 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 3) {
|
||||||
|
assert.match(sql, /FROM c_viajes_proveedor/);
|
||||||
|
return [[{ n_proveedor: 1 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 4) {
|
||||||
|
assert.match(sql, /FROM c_viajes_puntos/);
|
||||||
|
assert.deepEqual(params, [8123, 248230]);
|
||||||
|
return [[{ id_punto: 8123, id_estado_intermedio: 4, valor: null, foto: null }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /UPDATE c_viajes_puntos/);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
requestJson({
|
||||||
|
port: server.address().port,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/trips/248230/status',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
id_estado: 5,
|
||||||
|
id_punto: 8123,
|
||||||
|
latitud: '40.416775',
|
||||||
|
longitud: '-3.703790'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.equal(response.body.success, true);
|
||||||
|
assert.equal(response.body.agheera_push, undefined);
|
||||||
|
assert.equal(agheeraCalls.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/trips/:id/status fallo de Agheera mantiene respuesta 200', async () => {
|
||||||
|
process.env.AGHEERA_API_KEY = 'test-api-key';
|
||||||
|
const agheeraCalls = [];
|
||||||
|
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
||||||
|
agheeraCalls.push({ url, options });
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
status: 500,
|
||||||
|
text: async () => 'temporary error'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let step = 0;
|
||||||
|
db.query = async (sql) => {
|
||||||
|
step += 1;
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
return [[{ id_estado: 6 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 2) {
|
||||||
|
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 532 }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 3) {
|
||||||
|
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 4) {
|
||||||
|
assert.match(sql, /UPDATE c_viajes/);
|
||||||
|
return [{ affectedRows: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
||||||
|
return [{ insertId: 6, affectedRows: 1 }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await withServer(async (server) =>
|
||||||
|
requestJson({
|
||||||
|
port: server.address().port,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/trips/248230/status',
|
||||||
|
authorization: `Bearer ${createToken()}`,
|
||||||
|
body: {
|
||||||
|
id_estado: 6,
|
||||||
|
latitud: '40.416775',
|
||||||
|
longitud: '-3.703790'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert.equal(response.body.success, true);
|
||||||
|
assert.deepEqual(response.body.agheera_push, {
|
||||||
|
trip_id: 248230,
|
||||||
|
attempted: true,
|
||||||
|
success: false,
|
||||||
|
http_status: 500,
|
||||||
|
error: 'Agheera push failed'
|
||||||
|
});
|
||||||
|
assert.equal(agheeraCalls.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
test('POST /api/trips/:id/status estado intermedio con id_punto inválido => 400', async () => {
|
test('POST /api/trips/:id/status estado intermedio con id_punto inválido => 400', async () => {
|
||||||
db.query = async () => {
|
db.query = async () => {
|
||||||
throw new Error('db.query should not run for invalid id_punto');
|
throw new Error('db.query should not run for invalid id_punto');
|
||||||
@ -2523,7 +2795,7 @@ test('DELETE /api/trips/:id/status en modo dual no borra foto en remoto ni local
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user