137 lines
9.8 KiB
JavaScript
137 lines
9.8 KiB
JavaScript
// Tests du kilometrage (04/10/2026).
|
|
// node tests/kilometrage/run.mjs -> fonctions pures
|
|
// node tests/kilometrage/run.mjs --vues -> vues SQL, dans une transaction
|
|
// ANNULEE (a lancer dans le
|
|
// conteneur d'historisation)
|
|
import { lireReleves, ajouterReleve, creerSchemaKm, ingererJeep, JEEP } from '../../tools/kilometrage.mjs';
|
|
|
|
let ok = 0, ko = 0;
|
|
const eq = (nom, a, b) => {
|
|
const sa = JSON.stringify(a), sb = JSON.stringify(b);
|
|
if (sa === sb) { ok++; } else { ko++; console.log(`ECHEC ${nom}\n obtenu ${sa}\n attendu ${sb}`); }
|
|
};
|
|
const proche = (nom, a, b, tol = 1e-6) => eq(nom, Math.abs(Number(a) - b) <= tol ? b : Number(a), b);
|
|
|
|
// --- lireReleves
|
|
const T = 1759515246;
|
|
eq('vide', lireReleves('').releves, []);
|
|
eq('unknown', lireReleves('unknown').releves, []);
|
|
eq('json illisible', lireReleves('[x').rejets, ['JSON illisible']);
|
|
const r1 = lireReleves(JSON.stringify([['jeep', 112694, 54, T, 'w'], ['ZERO', 16469, null, T, 'w']]));
|
|
eq('deux releves', r1.releves.map(r => [r.vehicule, r.km, r.niveau, r.ts.getTime() / 1000]),
|
|
[['JEEP', 112694, 54, T], ['ZERO', 16469, null, T]]);
|
|
eq('km decimal refuse', lireReleves(JSON.stringify([['VW', 1.5, null, T, 'w']])).rejets.length, 1);
|
|
eq('niveau 150 refuse', lireReleves(JSON.stringify([['VW', 1000, 150, T, 'w']])).rejets.length, 1);
|
|
eq('horodatage absurde refuse', lireReleves(JSON.stringify([['VW', 1000, null, 12, 'w']])).rejets.length, 1);
|
|
|
|
// --- ajouterReleve : garde les derniers, tient dans 255 caracteres
|
|
let b = '';
|
|
for (let i = 0; i < 10; i++) b = ajouterReleve(b, { vehicule: 'JEEP', km: 112694 + i, niveau: 54, ts: new Date((T + i) * 1000), source: 'whatsapp' });
|
|
eq('255 car max', b.length <= 255, true);
|
|
eq('dernier garde', JSON.parse(b).at(-1)[1], 112703);
|
|
eq('helper corrompu repart a vide', JSON.parse(ajouterReleve('pas du json', { vehicule: 'VW', km: 5, niveau: null, ts: new Date(T * 1000), source: 'ha' })).length, 1);
|
|
|
|
// --- ingererJeep : base simulee en memoire (04/10/2026)
|
|
{
|
|
const base = { releves: [], niveaux: [], branche: new Date('2026-10-04T11:28:00Z') };
|
|
const sql = async (q, v) => {
|
|
if (q.includes('INSERT INTO releve_km')) { base.releves.push({ ts: v[0], km: v[1], niveau: v[2] }); return { rowCount: 1 }; }
|
|
if (q.includes('INSERT INTO niveau_saisi')) { base.niveaux.push({ saisi_a: v[0], niveau: v[1] }); return { rowCount: 1 }; }
|
|
return { rowCount: 1 };
|
|
};
|
|
const lire = async (q, v) => {
|
|
if (q.includes('FROM releve_km')) { const r = [...base.releves].sort((a, b) => b.ts - a.ts); return { rows: r.length ? [{ km: r[0].km, ts: r[0].ts }] : [] }; }
|
|
if (q.includes('FROM evenement')) return { rows: [{ t: base.branche }] };
|
|
if (q.includes('FROM niveau_saisi')) return { rows: base.niveaux.filter(n => n.saisi_a >= v[0]) };
|
|
return { rows: [] };
|
|
};
|
|
const journ = async () => {};
|
|
const appels = [];
|
|
const etiq = async (b, n) => { appels.push([b.toISOString(), n]); return 1; };
|
|
const etat = (km, niv, maj, branchee = 'on', borne = 'on') => new Map([
|
|
[JEEP.odometre, { state: String(km) }], [JEEP.niveau, { state: String(niv) }], [JEEP.majA, { state: maj }],
|
|
[JEEP.branchee, { state: branchee }], ['binary_sensor.victron_evcs_vehicle_connected', { state: borne }],
|
|
['input_number.victron_evcs_capacity_jeep_kwh', { state: '8.7' }], ['input_number.victron_evcs_autonomie_jeep_km', { state: '50' }]]);
|
|
await ingererJeep(sql, lire, etat(112716, 46, '2026-10-04T11:28:39+00:00'), journ, etiq);
|
|
eq('jeep : premier releve + niveau au branchement', [base.releves.length, base.niveaux.length, base.niveaux[0]?.niveau], [1, 1, 46]);
|
|
await ingererJeep(sql, lire, etat(112716, 60, '2026-10-04T11:58:39+00:00'), journ, etiq);
|
|
eq('jeep : compteur immobile et 2e maj du branchement -> rien', [base.releves.length, base.niveaux.length], [1, 1]);
|
|
eq('jeep : branchement identifie UNE fois, avec le niveau lu', appels, [['2026-10-04T11:28:00.000Z', 46]]);
|
|
base.branche = new Date('2026-10-05T16:00:00Z');
|
|
await ingererJeep(sql, lire, etat(112760, 30, '2026-10-05T17:00:00+00:00'), journ);
|
|
eq('jeep : trajet -> releve ; maj 1 h apres le branchement -> pas de niveau', [base.releves.length, base.releves[1]?.km, base.niveaux.length], [2, 112760, 1]);
|
|
await ingererJeep(sql, lire, etat(112700, 30, '2026-10-05T18:00:00+00:00'), journ);
|
|
eq('jeep : compteur en baisse refuse', base.releves.length, 2);
|
|
base.branche = new Date('2026-10-06T16:00:00Z');
|
|
await ingererJeep(sql, lire, etat(112800, 25, '2026-10-06T16:05:00+00:00', 'on', 'off'), journ, etiq);
|
|
eq('jeep : borne debranchee -> pas d identification', appels.length, 1);
|
|
eq('jeep : borne debranchee -> releve oui, niveau au branchement non', [base.releves.length, base.niveaux.length], [3, 1]);
|
|
}
|
|
|
|
if (process.argv.includes('--vues')) {
|
|
const pg = (await import('pg')).default;
|
|
const c = new pg.Client(); await c.connect();
|
|
try {
|
|
await c.query('BEGIN');
|
|
await creerSchemaKm(c);
|
|
// Vehicules fictifs, annee 2020 : rien ne se melange au reel, et tout est annule.
|
|
const ins = (v, ts, km, n, cap, auto, elec) => c.query(
|
|
'INSERT INTO releve_km (vehicule, ts, km, niveau_pct, source, capacite_kwh, autonomie_ref_km, tout_electrique) VALUES ($1,$2,$3,$4,\'test\',$5,$6,$7)',
|
|
[v, ts, km, n, cap, auto, elec]);
|
|
await ins('TESTH', '2020-01-01T18:00Z', 1000, 54, 8.7, 50, false);
|
|
await ins('TESTH', '2020-01-02T18:00Z', 1030, 20, 8.7, 50, false); // pas de charge : 34 % x 8,7 = 2,958 kWh
|
|
await ins('TESTH', '2020-01-04T18:00Z', 1080, 0, 8.7, 50, false); // batterie vide : essence possible
|
|
await ins('TESTE', '2020-01-01T18:00Z', 500, 80, 15, 200, true);
|
|
await ins('TESTE', '2020-01-03T18:00Z', 600, 0, 15, 200, true); // electrique : 0 % reste valide
|
|
await c.query(`INSERT INTO session_charge (debut, fin, terminee, vehicule, vehicule_sur, energie_kwh, energie_station_kwh)
|
|
VALUES ('2020-01-03T01:00Z','2020-01-03T03:00Z',true,'TESTH',true,4.0,4.2)`);
|
|
const { rows } = await c.query("SELECT * FROM km_intervalle WHERE vehicule LIKE 'TEST%' ORDER BY vehicule, debut");
|
|
const h1 = rows.find(r => r.vehicule === 'TESTH' && r.km_parcourus === 30);
|
|
proche('H1 kWh consommes = 0 + 0,34 x 8,7', h1.kwh_consommes, 2.958);
|
|
proche('H1 km/kWh', h1.km_par_kwh, 30 / 2.958);
|
|
proche('ref km/kWh = 50/8,7', h1.km_par_kwh_ref, 50 / 8.7);
|
|
eq('H1 valide', h1.statut, 'valide');
|
|
const h2 = rows.find(r => r.vehicule === 'TESTH' && r.km_parcourus === 50);
|
|
proche('H2 charge de la borne comptee (4,2)', h2.kwh_charges, 4.2);
|
|
eq('H2 hybride vide au retour', h2.statut, 'batterie vide au retour : km essence possibles');
|
|
const e1 = rows.find(r => r.vehicule === 'TESTE');
|
|
proche('E1 kWh = 0,80 x 15', e1.kwh_consommes, 12);
|
|
eq('E1 electrique a 0 % valide', e1.statut, 'valide');
|
|
|
|
// Capacite : branchement 2020-02-01 10:00 -> 14:00, niveau 50 % saisi a
|
|
// 10:40 (compteur deja a 101,0 : la voiture chargeait), compteur 100,0 au
|
|
// branchement et 104,5 au debranchement, plein vu a 13:00.
|
|
const ev = (ts, apres) => c.query(`INSERT INTO evenement (ts, genre, source, resume, detail) VALUES ($1,'vehicule','test','test',$2)`,
|
|
[ts, JSON.stringify({ entite: 'binary_sensor.victron_evcs_vehicle_connected', apres })]);
|
|
const cpt = (ts, v) => c.query(`INSERT INTO mesure (ts, cle, valeur) VALUES ($1,'sensor.evcs_hq2239dnt7p_total_energy',$2)`, [ts, v]);
|
|
await ev('2020-02-01T10:00:00.123456Z', 'on'); await ev('2020-02-01T14:00Z', 'off');
|
|
await c.query(`INSERT INTO etiquette_branchement (branchement, label, source_airtria) VALUES ('2020-02-01T10:00:00.123Z','TESTH','test')`);
|
|
await c.query(`INSERT INTO niveau_saisi (saisi_a, niveau_pct, ref_kwh) VALUES ('2020-02-01T10:40Z', 50, 101.0)`);
|
|
await cpt('2020-02-01T09:59Z', 100.0); await cpt('2020-02-01T13:59Z', 104.5);
|
|
await c.query(`INSERT INTO decision (ts, etat) VALUES ('2020-02-01T13:00Z','day_vehicle_full_idle')`);
|
|
const capa = async () => (await c.query("SELECT * FROM capacite_estimee WHERE branche >= '2020-02-01' AND branche < '2020-02-02'")).rows[0];
|
|
let cap = await capa();
|
|
proche('energie depuis le branchement, pas la saisie', cap.kwh_livres, 4.5);
|
|
proche('capacite = 4,5 / 0,5', cap.capacite_kwh, 9.0);
|
|
eq('vehicule de l etiquette (horodatage a la microseconde pres)', cap.vehicule, 'TESTH');
|
|
await c.query(`INSERT INTO session_charge (debut, fin, terminee, vehicule, vehicule_sur, energie_kwh, energie_station_kwh)
|
|
VALUES ('2020-02-01T10:01Z','2020-02-01T13:00Z',true,'TESTX',true,4.4,4.5)`);
|
|
eq('la session confirmee prime sur l etiquette', (await capa()).vehicule, 'TESTX');
|
|
await c.query(`DELETE FROM decision WHERE ts = '2020-02-01T13:00Z'`);
|
|
cap = await capa();
|
|
eq('pas plein, pas de releve -> pas de capacite', cap.capacite_kwh, null);
|
|
await ins('TESTX', '2020-02-01T14:20Z', 777, 80, 15, 200, true);
|
|
proche('releve dans l heure : 4,5 / (80-50) %', (await capa()).capacite_kwh, 15.0);
|
|
// Saisie ancienne sans compteur historise : somme des sessions du branchement.
|
|
await ev('2020-03-01T20:00Z', 'on'); await ev('2020-03-02T04:00Z', 'off');
|
|
await c.query(`INSERT INTO session_charge (debut, fin, terminee, vehicule, vehicule_sur, energie_kwh, energie_station_kwh)
|
|
VALUES ('2020-03-01T23:25Z','2020-03-02T01:41Z',true,'TESTV',true,7.6,7.7)`);
|
|
await c.query(`INSERT INTO niveau_saisi (saisi_a, niveau_pct, ref_kwh, source) VALUES ('2020-03-01T20:00:01Z', 5, NULL, 'test')`);
|
|
await c.query(`INSERT INTO decision (ts, etat) VALUES ('2020-03-02T01:42Z','night_recovery_done_idle')`);
|
|
const v = (await c.query("SELECT * FROM capacite_estimee WHERE branche >= '2020-03-01' AND branche < '2020-03-02'")).rows[0];
|
|
proche('sans compteur : sessions du branchement 7,7 / 0,95', v.capacite_kwh, 7.7 / 0.95);
|
|
} finally { await c.query('ROLLBACK'); await c.end(); }
|
|
}
|
|
console.log(`${ok}/${ok + ko}`);
|
|
process.exit(ko ? 1 : 0);
|