// Tests du paquet lui-même : version, schémas en phase, tables de coefficients // bien formées, pureté du code (aucune I/O, aucune horloge, aucun aléa dans // src/), et absence des anciennes constantes dupliquées (LOT P1.0 point 6). import { test } from 'node:test' import assert from 'node:assert/strict' import { readFileSync, readdirSync, statSync } from 'node:fs' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { VERSION, SCHEMAS, NOMS_TABLES, COEFFICIENTS, verifieTableCoefficients, MOTEURS } from '../dist/index.js' const racine = fileURLToPath(new URL('..', import.meta.url)) const lire = (rel) => readFileSync(join(racine, rel), 'utf8') function fichiersTs(dossier) { const out = [] for (const nom of readdirSync(dossier)) { const chemin = join(dossier, nom) if (statSync(chemin).isDirectory()) out.push(...fichiersTs(chemin)) else if (nom.endsWith('.ts')) out.push(chemin) } return out } /** Lignes de code hors commentaires (// et /* *\/ sur une ligne) et hors chaînes de `note:`/`source:`. */ function lignesDeCode(texte) { return texte .split('\n') .map((l) => l.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '')) .filter((l) => l.trim() && !/^\s*\*/.test(l) && !/^\s*(note|source):\s*['"`]/.test(l)) } test('VERSION du code = version de package.json', () => { const pkg = JSON.parse(lire('package.json')) assert.equal(VERSION, pkg.version) assert.equal(pkg.name, '@transformer/moteurs') assert.deepEqual(pkg.dependencies ?? {}, {}, 'zéro dépendance de production') }) test('schemas/*.schema.json en phase avec src/commun/schemas.ts', () => { for (const table of NOMS_TABLES) { const json = JSON.parse(lire(`schemas/${table}.schema.json`)) delete json.$schema assert.deepEqual(json, JSON.parse(JSON.stringify(SCHEMAS[table])), `${table}.schema.json diffère de SCHEMAS.${table} : relancer npm run build`) } }) test('les deux tables de coefficients sont complètes, sourcées et datées', () => { assert.deepEqual(verifieTableCoefficients(COEFFICIENTS), []) const origine = JSON.parse(lire('fixtures/coefficients-2026-05.json')) assert.deepEqual(verifieTableCoefficients(origine), []) for (const cle of Object.keys(COEFFICIENTS)) assert.ok(cle in origine, `fixture d'origine : clé ${cle} manquante`) }) test('six moteurs livrés, identifiants stables', () => { assert.deepEqual([...MOTEURS], ['commun.contexte', 'commun.geometrie', 'autonomie.eau', 'autonomie.energie', 'autonomie.alimentation', 'autonomie.integrateur']) }) test('pureté : src/ ne lit ni réseau, ni disque, ni horloge, ni DOM, ni aléa, ni console', () => { const interdits = [ [/\bfetch\s*\(/, 'fetch'], [/\brequire\s*\(/, 'require'], [/from\s+['"]node:/, 'import node:'], [/from\s+['"](fs|path|os|http|https|child_process)['"]/, 'module Node'], [/\bprocess\./, 'process'], [/\bDate\.now\b|\bnew Date\b/, 'horloge'], [/\bMath\.random\b/, 'aléa'], [/\b(localStorage|sessionStorage|document|window)\b/, 'DOM'], [/\bconsole\./, 'console'], [/\bimport\s.*\.json['"]/, 'import JSON (les données s\'injectent)'], ] const fautes = [] for (const f of fichiersTs(join(racine, 'src'))) { const lignes = lignesDeCode(readFileSync(f, 'utf8')) for (const [re, nom] of interdits) { const l = lignes.find((x) => re.test(x)) if (l) fautes.push(`${f.slice(racine.length)} : ${nom} — ${l.trim()}`) } } assert.deepEqual(fautes, []) }) test('anciennes constantes : plus aucune définition en double dans src/', () => { // Chaque grandeur existe UNE fois, dans coefficients.ts (LOT P1.0 point 6). const patrons = [ [/ORIENTATION_FACTORS|DJU_DEFAULTS_BY_ZONE|FALLBACK_PVGIS|RENDEMENT_CAPTAGE|BESOIN_EAU_L_PERS_AN|PART_SUBSTITUABLE|BESOIN_ELEC_KWH_PERS_AN|ML_HAIE_PAR_PERS/, 'constante de module d\'origine'], [/\b0\.93\b|\b0\.78\b|\b0\.40\b/, 'facteurs d\'orientation d\'origine'], [/\b1550\b|\b1480\b|\b54750\b/, 'productible 1 550 / 1 480 ou besoin 54 750'], [/\(pluvio \/ 1000\) \* 0\.7\b/, 'rendement de captage 0,7 dans score_pluviometrie'], [/\b0\.85\b/, 'rendement 0,85'], ] const fautes = [] for (const f of fichiersTs(join(racine, 'src'))) { if (f.endsWith('coefficients.ts')) continue const lignes = lignesDeCode(readFileSync(f, 'utf8')) for (const [re, nom] of patrons) { const l = lignes.find((x) => re.test(x)) if (l) fautes.push(`${f.slice(racine.length)} : ${nom} — ${l.trim()}`) } } assert.deepEqual(fautes, []) // Dans coefficients.ts, les anciennes valeurs n'apparaissent qu'en note (chaîne), jamais en valeur. const coefs = lignesDeCode(lire('src/commun/coefficients.ts')).join('\n') assert.ok(!/valeur:\s*0\.85\b/.test(coefs) && !/\b1550\b/.test(coefs) && !/nord:\s*0\.4\b/.test(coefs), 'coefficients.ts porte une valeur d\'origine') })