@mostajs/mjs-unit 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -115,6 +115,54 @@ Le compte rendu lie les résultats à la version (lue dans la SBOM), au plan et
115
115
  est `conforme`, `non conforme` ou `incomplet` (avec les défauts) ; il s'écrit toujours. qatrax.dev l'enregistre, le
116
116
  relie à l'analyse des vulnérabilités de la même SBOM, et scelle le dossier.
117
117
 
118
+ ## Java, C#, Python, Go, C++… — le mode lecteur (0.10.0)
119
+
120
+ Vos essais restent joués par l'outil de votre langage ; mjs-unit **lit** leur rapport et produit le même compte
121
+ rendu scellé (lié à la SBOM et au plan), avec CTRF et in-toto. Node ≥ 18 suffit dans la CI ; aucun JavaScript à écrire.
122
+
123
+ **Formats lus tels quels, reconnus à leur contenu** : JUnit XML · NUnit 2 et 3 · TRX (VSTest) · TestNG natif ·
124
+ Robot Framework (`output.xml`) · CTest (`Test.xml`) · Cucumber JSON · Behave JSON · Allure 2 (`allure-results`) ·
125
+ CTRF · TAP · JSON mjs-unit. Votre rapport natif suffit : aucune conversion.
126
+
127
+ | Langage | Rapport | Commande |
128
+ |---|---|---|
129
+ | Java (Maven) | `target/surefire-reports/TEST-*.xml` | `npx @mostajs/mjs-unit --depuis=target/surefire-reports --sbom=target/bom.json --plan=plan.json --compte-rendu` |
130
+ | Java (Gradle) | `build/test-results/test/TEST-*.xml` | `npx @mostajs/mjs-unit --depuis=build/test-results/test …` |
131
+ | C# (.NET) | `dotnet test --logger "junit;LogFilePath=resultats.xml"` | `npx @mostajs/mjs-unit --depuis=resultats.xml …` |
132
+ | Python | `pytest --junitxml=resultats.xml` | `npx @mostajs/mjs-unit --depuis=resultats.xml …` |
133
+ | Go | `go test -v ./... 2>&1 \| go-junit-report > resultats.xml` | `npx @mostajs/mjs-unit --depuis=resultats.xml …` |
134
+ | C# (NUnit) | `TestResult.xml` (nunit3-console) | `npx @mostajs/mjs-unit --depuis=TestResult.xml …` |
135
+ | C# (TRX) | `dotnet test --logger trx` → `TestResults/*.trx` | `npx @mostajs/mjs-unit --depuis=TestResults …` |
136
+ | Java (TestNG) | `target/surefire-reports/testng-results.xml` | `npx @mostajs/mjs-unit --depuis=target/surefire-reports/testng-results.xml …` |
137
+ | Robot Framework | `output.xml` | `npx @mostajs/mjs-unit --depuis=output.xml …` |
138
+ | C/C++ (CTest) | `ctest -T Test` → `Testing/<date>/Test.xml` | `npx @mostajs/mjs-unit --depuis Testing/*/Test.xml …` |
139
+ | BDD (Cucumber) | `--format json:cucumber.json` (JS, JVM, Ruby) | `npx @mostajs/mjs-unit --depuis=cucumber.json …` |
140
+ | BDD (Behave) | `behave -f json -o behave.json` | `npx @mostajs/mjs-unit --depuis=behave.json …` |
141
+ | Allure (tout langage) | `allure-results/` | `npx @mostajs/mjs-unit --depuis=allure-results …` (conteneurs et pièces jointes écartés ; un essai rejoué : le dernier fait foi) |
142
+
143
+ Nommez vos essais d'après le plan : `test_T_MOD_01_…` est rattaché à `T-MOD-01`. En BDD, Robot ou Allure, une
144
+ **étiquette** égale à la référence suffit (`@T-MOD-01`). Avec le menu :
145
+ `MJS_DEPUIS=target/surefire-reports npx mjs-unit-menu --choix=3`, puis `--choix=4` (plan, SBOM, compte rendu vers
146
+ qatrax.dev). Les lecteurs sont exportés : `import { lireRapportEssais, refDuPlan } from "@mostajs/mjs-unit/lecteurs"`.
147
+
148
+ Ce que le sceau prouve : le rapport n'a pas changé **après sa lecture**, avec **cette** SBOM et **ce** plan. Il ne
149
+ prouve pas que l'outil du langage a joué les essais honnêtement.
150
+
151
+ ## La boucle avec qatrax (0.11.0)
152
+
153
+ ```bash
154
+ npx mjs-unit qatrax etat # « 2 nouveau(x), 1 changé(s) en recette » — rien d'autre si rien n'a bougé
155
+ npx mjs-unit qatrax rapatrier # la fiche des défauts et les captures, dans docs/recette/defauts/
156
+ npx mjs-unit qatrax pris <id> # puis réparer, écrire l'essai, npm test
157
+ npx mjs-unit qatrax corrige <id> # « corrigé dans <projet> <version> — à rejouer en recette »
158
+ npx mjs-unit qatrax pousser --si-vert # plan, run, SBOM — seulement si le dernier run est vert
159
+ ```
160
+
161
+ Dans le `package.json` du projet : `"qatrax": { "projet": "speaking", "url": "https://speaking.qatrax.dev", "nom": "Speaking" }`.
162
+ La clé de CI (opération « pousser ») : `QATRAX_CLE_CI`, sinon `~/.config/qatrax/cle-ci-<projet>` en mode 600.
163
+ qatrax prévient de son côté (webhook signé, courriel) quand un rapport de recette arrive. En code :
164
+ `import { configQatrax, creerClientQatrax } from '@mostajs/mjs-unit/qatrax'`.
165
+
118
166
  ## API
119
167
 
120
168
  | Fonction | Rôle |
package/bin/cli.mjs CHANGED
@@ -17,15 +17,28 @@
17
17
  * TOUJOURS — même invalide, avec ses défauts ; le code de sortie reste celui des essais
18
18
  * --ctrf[=f] export CTRF (défaut : test-scripts/.out/ctrf.json)
19
19
  * --intoto[=f] énoncé in-toto test-result v0.1, non signé (défaut : test-scripts/.out/test-result.intoto.json)
20
+ * 0.10.0 — LE MODE LECTEUR (Java, C#, Python, Go) : aucun essai exécuté, le rapport de l'outil du langage est LU
21
+ * --depuis les arguments sont des RAPPORTS : mjs-unit --depuis target/surefire-reports/*.xml
22
+ * JUnit XML, NUnit 2/3, TRX, TestNG, Robot Framework, CTest, Cucumber JSON, Behave JSON,
23
+ * Allure 2, CTRF, TAP, JSON mjs-unit — reconnus à leur contenu
24
+ * 0.11.0 — LE CLIENT QATRAX : mjs-unit qatrax <etat|rapatrier|pris|corrige|pousser> (voir : mjs-unit qatrax aide)
25
+ * --depuis=chemin un dossier (tous ses .xml, .json, .tap, .trx ; dans allure-results, les *-container et
26
+ * *-attachment sont écartés) ou un fichier
27
+ * → les mêmes sorties : --compte-rendu, --ctrf, --intoto, --json, --junit, --report
20
28
  *
21
29
  * @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
22
30
  */
31
+ // 0.11.0 — `mjs-unit qatrax …` : le client qatrax, chargé SEULEMENT pour cette sous-commande (le runner reste sans réseau).
32
+ if (process.argv[2] === "qatrax") {
33
+ const { principal } = await import("../dist/qatrax.js");
34
+ process.exit(await principal(process.argv.slice(3)));
35
+ }
23
36
  import { register } from "node:module";
24
37
  import { pathToFileURL } from "node:url";
25
- import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
38
+ import { writeFileSync, readFileSync, mkdirSync, statSync, readdirSync } from "node:fs";
26
39
  import { hostname } from "node:os";
27
40
  import { createHash } from "node:crypto";
28
- import { dirname } from "node:path";
41
+ import { dirname, join } from "node:path";
29
42
 
30
43
  register(new URL("../loader.mjs", import.meta.url));
31
44
  const { run, pending, registered, takeRuns, setDefaultTimeout, setTagFilter } = await import("../dist/index.js");
@@ -45,9 +58,11 @@ const title = opt("title");
45
58
  if (opt("timeout")) setDefaultTimeout(Number(opt("timeout")));
46
59
  if (opt("tag")) setTagFilter(opt("tag").split(","));
47
60
  const crOut = opt("compte-rendu"), ctrfOut = opt("ctrf"), intotoOut = opt("intoto"), sbomIn = opt("sbom"), planIn = opt("plan");
61
+ const depuis = opt("depuis"); // 0.10.0 — mode lecteur : "" (les arguments sont des rapports) ou un chemin
62
+ const lecteur = depuis !== undefined;
48
63
  const files = argv.filter((a) => !a.startsWith("--"));
49
64
  const debut = new Date().toISOString();
50
- if (!files.length) { console.error("usage: mjs-unit <fichier-de-test...> [--report[=f]] [--json[=f]]"); process.exit(2); }
65
+ if (!files.length && !depuis) { console.error("usage: mjs-unit <fichier-de-test...> [--report[=f]] [--json[=f]] — ou : mjs-unit --depuis <rapport...> (JUnit, NUnit, TRX, TestNG, Robot, CTest, Cucumber, Behave, Allure, CTRF, TAP)"); process.exit(2); }
51
66
 
52
67
  // ── Exécution, une suite par fichier ─────────────────────────────────────────
53
68
  // Le CLI DEMANDE au module ce qui a été enregistré et ce qui a été exécuté, fichier par fichier : il
@@ -55,7 +70,45 @@ if (!files.length) { console.error("usage: mjs-unit <fichier-de-test...> [--repo
55
70
  // hors du rapport, et un fichier sans aucun test disparaissait sans un mot.
56
71
  let totalFail = 0, totalDeclares = 0, totalExecutes = 0;
57
72
  const suites = [];
58
- for (const f of files) {
73
+ const sha = (b) => createHash("sha256").update(b).digest("hex");
74
+ // ── 0.10.0 — LE MODE LECTEUR : lire le rapport de l'outil du langage (Surefire, pytest, xUnit, go test…) ─────────
75
+ // Aucun essai n'est exécuté : les suites sont REMPLIES depuis les rapports, puis toute la chaîne suit (compte rendu,
76
+ // CTRF, in-toto, JSON, JUnit, HTML). Un rapport illisible est un défaut DIT ; un statut incompris est une erreur.
77
+ const lecture = lecteur ? { rapports: [], defauts: [] } : null;
78
+ let genereLe = null;
79
+ if (lecteur) {
80
+ const { lireRapportEssais } = await import("../dist/lecteurs.js");
81
+ const RAPPORT = /\.(xml|json|tap|trx)$/i;
82
+ const ANNEXE = /-(container|attachment)\.[^.]+$/i; // Allure : fixtures et pièces jointes, pas des résultats
83
+ const sources = depuis ? (statSync(depuis, { throwIfNoEntry: false })?.isDirectory() ? readdirSync(depuis).filter((n) => RAPPORT.test(n) && !ANNEXE.test(n)).sort().map((n) => join(depuis, n)) : [depuis]) : files;
84
+ if (!sources.length) { console.error(`✗ --depuis : aucun rapport (.xml, .json, .tap, .trx) dans ${depuis}`); process.exit(2); }
85
+ const STATUT = { pass: ["pass"], fail: ["fail", "failure"], skipped: ["skip"], blocked: ["fail", "error"] };
86
+ const ajouter = (rapport, f) => {
87
+ if (rapport.generatedAt && (!genereLe || rapport.generatedAt < genereLe)) genereLe = rapport.generatedAt;
88
+ for (const s of rapport.suites) {
89
+ const results = s.results.map((r) => { const [status, kind] = STATUT[r.status] ?? STATUT.blocked;
90
+ return { name: r.name, title: r.name, status, durationMs: r.durationMs ?? 0, ...(kind ? { kind } : {}), ...(r.error ? { error: r.error } : {}), ...(r.tags?.length ? { tags: r.tags } : {}),
91
+ ...(r.status === "blocked" ? { error: r.error ?? "statut incompris : ni réussi, ni échoué, ni ignoré" } : {}), ...(status === "skip" ? { skipReason: r.error ?? "ignoré par l'outil d'essai" } : {}) }; });
92
+ totalFail += results.filter((r) => r.status === "fail").length;
93
+ totalDeclares += results.length; totalExecutes += results.length;
94
+ suites.push({ file: s.file || f, results });
95
+ }
96
+ };
97
+ // Allure : un fichier par essai — lus ENSEMBLE, pour qu'un essai rejoué (même historyId) ne compte qu'une fois
98
+ const allure = []; let allureSource = null;
99
+ for (const f of sources) {
100
+ let brut;
101
+ try { brut = readFileSync(f); } catch (e) { lecture.defauts.push(`rapport illisible : ${f} — ${e?.code ?? e?.message ?? e}`); totalFail++; continue; }
102
+ const lu = lireRapportEssais(brut.toString("utf8"));
103
+ lecture.rapports.push({ fichier: f, format: lu.format, sha256: sha(brut) });
104
+ if (!lu.rapport) { lecture.defauts.push(`rapport illisible : ${f} — ${lu.erreur ?? lu.format}`); console.error(`✗ ${f} : ${lu.erreur ?? lu.format}`); totalFail++; continue; }
105
+ if (lu.format === "allure") { allure.push(...(Array.isArray(lu.brut) ? lu.brut : [lu.brut])); allureSource ??= depuis && statSync(depuis).isDirectory() ? depuis : f; continue; }
106
+ ajouter(lu.rapport, f);
107
+ }
108
+ if (allure.length) ajouter(lireRapportEssais(allure).rapport, allureSource);
109
+ console.log(`\n${totalFail ? "✗" : "✓"} ${totalExecutes} cas lu(s) dans ${lecture.rapports.length} rapport(s) — mode lecteur : aucun essai exécuté par mjs-unit`);
110
+ }
111
+ for (const f of lecteur ? [] : files) {
59
112
  const avant = registered();
60
113
  takeRuns(); // rien d'un fichier précédent ne doit être attribué à celui-ci
61
114
  await import(pathToFileURL(f).href); // le fichier enregistre ses cas (et peut appeler run() lui-même)
@@ -78,12 +131,12 @@ for (const f of files) {
78
131
  totalDeclares += declares; totalExecutes += cas.length;
79
132
  suites.push({ file: f, results });
80
133
  }
81
- console.log(`\n${totalExecutes === totalDeclares ? "✓" : "✗"} ${totalExecutes}/${totalDeclares} test(s) exécutés sur ${suites.length} fichier(s) — compte relevé auprès du module, pas supposé`);
134
+ if (!lecteur) console.log(`\n${totalExecutes === totalDeclares ? "✓" : "✗"} ${totalExecutes}/${totalDeclares} test(s) exécutés sur ${suites.length} fichier(s) — compte relevé auprès du module, pas supposé`);
82
135
 
83
136
  // ── Reporters optionnels ─────────────────────────────────────────────────────
84
137
  if (htmlOut !== undefined || jsonOut !== undefined || junitOut !== undefined) {
85
138
  const { renderHtmlReport, renderJsonReport, renderJunitReport } = await import("../dist/report.js");
86
- const report = { suites, generatedAt: new Date().toISOString(), title: title || undefined, hostname: hostname() };
139
+ const report = { suites, generatedAt: genereLe ?? new Date().toISOString(), title: title || undefined, hostname: hostname() };
87
140
  if (htmlOut !== undefined) {
88
141
  const out = htmlOut || "test-report.html";
89
142
  writeFileSync(out, renderHtmlReport(report));
@@ -104,18 +157,18 @@ if (htmlOut !== undefined || jsonOut !== undefined || junitOut !== undefined) {
104
157
  // ── 0.9.0 — le compte rendu de validation, écrit TOUJOURS quand il est demandé (même invalide) ─────────────
105
158
  if (crOut !== undefined || ctrfOut !== undefined || intotoOut !== undefined) {
106
159
  const { construireCompteRendu, renderCtrf, renderInTotoTestResult } = await import("../dist/report.js");
107
- const sha = (b) => createHash("sha256").update(b).digest("hex");
108
160
  const piece = (f) => { const b = readFileSync(f); return { fichier: f, sha256: sha(b), contenu: JSON.parse(b.toString("utf8")) }; };
109
161
  const lire = (f) => { if (!f) return { p: null }; try { return { p: piece(f) }; } catch (e) { return { p: null, e: `${f} — ${e?.code ?? e?.message ?? e}` }; } };
110
162
  const s = lire(sbomIn), pl = lire(planIn);
111
163
  const moi = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
112
164
  const env = process.env;
113
165
  const ci = env.CI_PIPELINE_URL || env.CI_JOB_URL || (env.GITHUB_RUN_ID && env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}` : undefined);
114
- const cr = construireCompteRendu({ suites, generatedAt: new Date().toISOString(), title: title || undefined, hostname: hostname() }, {
166
+ const cr = construireCompteRendu({ suites, generatedAt: genereLe ?? new Date().toISOString(), title: title || undefined, hostname: hostname() }, {
115
167
  sbom: s.p, sbomErreur: s.e, plan: pl.p, planErreur: pl.e,
116
- scripts: files.map((f) => { try { return { fichier: f, sha256: sha(readFileSync(f)) }; } catch { return { fichier: f, sha256: "" }; } }),
168
+ scripts: lecteur ? [] : files.map((f) => { try { return { fichier: f, sha256: sha(readFileSync(f)) }; } catch { return { fichier: f, sha256: "" }; } }),
169
+ lecture,
117
170
  environnement: { node: process.version, plateforme: `${process.platform}-${process.arch}`, hote: hostname(), ...(ci ? { ci } : {}), debut, fin: new Date().toISOString() },
118
- declares: totalDeclares, outil: { name: moi.name, version: moi.version },
171
+ declares: totalDeclares, outil: { name: moi.name, version: moi.version, ...(lecteur ? { mode: "lecteur" } : {}) },
119
172
  });
120
173
  const ecrire = (f, texte) => { mkdirSync(dirname(f), { recursive: true }); writeFileSync(f, texte); return sha(texte); };
121
174
  const exports_ = {};
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # mjs-unit-menu — le cycle de validation en un menu (0.9.0) : essais → SBOM → compte rendu → qatrax.dev.
3
+ # mjs-unit-menu — le cycle de validation en un menu (0.10.0) : essais → SBOM → compte rendu → qatrax.dev.
4
4
  #
5
5
  # 1) lancer les essais (rapports HTML, JSON, JUnit)
6
6
  # 2) générer ou continuer la SBOM (npx mjs-sbom --diff)
7
7
  # 3) compte rendu complet : SBOM + plan + essais + CTRF + in-toto
8
- # 4) envoyer à qatrax.dev : POST /sbom puis POST /runs (le compte rendu), clé de CI du projet
8
+ # (MJS_DEPUIS posé : les essais ne sont pas exécutés, le rapport de l'outil du langage est LU — Java, C#, Python, Go, C++ : JUnit, NUnit, TRX, TestNG, Robot, CTest, Cucumber, Behave, Allure…)
9
+ # 4) envoyer à qatrax.dev : POST /import (le plan, s'il existe), POST /sbom, puis POST /runs (le compte rendu)
9
10
  # 5) lire les vulnérabilités de la version sur qatrax.dev (GET /vulnerabilites)
10
11
  # 6) afficher le dernier compte rendu (verdict, défauts)
11
12
  #
12
13
  # Sans interaction : mjs-unit-menu --choix=N (CI, scripts).
13
- # Variables : MJS_TESTS (fichiers d'essai, motif shell ; défaut test-scripts/**/*.test.mjs), MJS_SBOM (défaut
14
+ # Variables : MJS_TESTS (fichiers d'essai, motif shell ; défaut test-scripts/**/*.test.mjs), MJS_DEPUIS (0.10 : rapports
15
+ # d'un autre langage — dossier, fichier ou motif shell : target/surefire-reports, resultats.xml), MJS_SBOM (défaut
14
16
  # sbom.cyclonedx.json), MJS_PLAN (défaut : premier docs/DEVTEST-PLAN*.json), MJS_OUT (défaut test-scripts/.out),
15
17
  # QATRAX_URL, QATRAX_CLE (clé de CI, lue dans l'environnement, jamais écrite), QATRAX_PROJET.
16
18
  #
@@ -28,6 +30,7 @@ CR="$OUT/compte-rendu.json"
28
30
  essais() { local motif="${MJS_TESTS:-test-scripts/**/*.test.mjs}"; local f; f=( $motif ); printf '%s\n' "${f[@]}"; }
29
31
 
30
32
  lancer_essais() {
33
+ if [ -n "${MJS_DEPUIS:-}" ]; then echo "ℹ MJS_DEPUIS posé : lancez vos essais avec votre outil (mvn test, dotnet test, pytest, go test, robot, ctest, cucumber, behave…), puis choix 3 — mjs-unit LIT leur rapport"; return 0; fi
31
34
  mkdir -p "$OUT"; mapfile -t F < <(essais)
32
35
  [ ${#F[@]} -gt 0 ] || { echo "✗ aucun fichier d'essai (MJS_TESTS)"; return 2; }
33
36
  node "$CLI" "${F[@]}" --report="$OUT/report.html" --json="$OUT/report.json" --junit="$OUT/report.xml"
@@ -38,8 +41,13 @@ generer_sbom() {
38
41
  }
39
42
 
40
43
  compte_rendu() {
41
- mkdir -p "$OUT"; mapfile -t F < <(essais)
42
- [ ${#F[@]} -gt 0 ] || { echo "✗ aucun fichier d'essai (MJS_TESTS)"; return 2; }
44
+ mkdir -p "$OUT"
45
+ if [ -n "${MJS_DEPUIS:-}" ]; then
46
+ if [ -d "$MJS_DEPUIS" ] || [ -f "$MJS_DEPUIS" ]; then F=("--depuis=$MJS_DEPUIS"); else F=(--depuis $MJS_DEPUIS); fi
47
+ else
48
+ mapfile -t F < <(essais)
49
+ [ ${#F[@]} -gt 0 ] || { echo "✗ aucun fichier d'essai (MJS_TESTS)"; return 2; }
50
+ fi
43
51
  local opts=(--compte-rendu="$CR" --ctrf="$OUT/ctrf.json" --intoto="$OUT/test-result.intoto.json" --json="$OUT/report.json" --junit="$OUT/report.xml")
44
52
  [ -f "$SBOM" ] && opts+=(--sbom="$SBOM") || echo "⚠ pas de SBOM ($SBOM) : le compte rendu sera « incomplet » (choix 2)"
45
53
  [ -n "$PLAN" ] && [ -f "$PLAN" ] && opts+=(--plan="$PLAN")
@@ -49,9 +57,9 @@ compte_rendu() {
49
57
  # Les appels à qatrax.dev passent par Node (fetch) : pas de dépendance à curl, et la clé ne paraît dans aucune ligne de commande.
50
58
  qatrax() { # $1 = action (envoyer | vulnerabilites)
51
59
  : "${QATRAX_URL:?QATRAX_URL manquante}" "${QATRAX_CLE:?QATRAX_CLE manquante (clé de CI du projet)}" "${QATRAX_PROJET:?QATRAX_PROJET manquant}"
52
- ACTION="$1" SBOM="$SBOM" CR="$CR" node --input-type=module -e '
60
+ ACTION="$1" SBOM="$SBOM" CR="$CR" PLAN="$PLAN" node --input-type=module -e '
53
61
  import { readFileSync, existsSync } from "node:fs";
54
- const { QATRAX_URL: base, QATRAX_CLE: cle, QATRAX_PROJET: p, ACTION, SBOM, CR } = process.env;
62
+ const { QATRAX_URL: base, QATRAX_CLE: cle, QATRAX_PROJET: p, ACTION, SBOM, CR, PLAN } = process.env;
55
63
  const e = encodeURIComponent; const auth = { authorization: `Bearer ${cle}` };
56
64
  if (!existsSync(CR)) { console.error(`✗ pas de compte rendu (${CR}) : choix 3 d’abord`); process.exit(2); }
57
65
  const cr = JSON.parse(readFileSync(CR, "utf8")); const v = cr.produit?.version ?? "inconnue";
@@ -61,6 +69,8 @@ qatrax() { # $1 = action (envoyer | vulnerabilites)
61
69
  };
62
70
  if (ACTION === "envoyer") {
63
71
  let bon = true;
72
+ // 0.10.0 — le plan d’abord : sans lui, qatrax.dev ne sait pas quelle exigence chaque essai prouve.
73
+ if (PLAN && existsSync(PLAN)) bon = (await appel("POST", "/import", readFileSync(PLAN, "utf8"))).ok && bon;
64
74
  if (existsSync(SBOM)) bon = (await appel("POST", `/sbom?p=${e(p)}&version=${e(v)}`, readFileSync(SBOM, "utf8"))).ok && bon;
65
75
  else console.log(`⚠ pas de SBOM (${SBOM}) : seul le compte rendu part — qatrax.dev le consignera « SBOM non reçue »`);
66
76
  bon = (await appel("POST", `/runs?p=${e(p)}&format=json`, readFileSync(CR, "utf8"))).ok && bon;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @mostajs/mjs-unit/lecteurs — LIRE UN RAPPORT D'ESSAIS, QUEL QU'EN SOIT L'OUTIL (0.10.0).
3
+ * @author Dr Hamid MADANI <drmdh@msn.com>
4
+ * @license AGPL-3.0-or-later
5
+ *
6
+ * Descendus de @mostajs/qa-engine/formats (DEVRULES R-G : la logique partagée vit à la couche la plus basse) pour le
7
+ * mode lecteur de mjs-unit (`--depuis`) : un projet Java, C#, Python ou Go garde son outil d'essai, et mjs-unit LIT
8
+ * son rapport pour en faire le compte rendu de validation. qa-engine les reprend d'ici.
9
+ *
10
+ * JUnit XML (Surefire, Gradle, pytest, xUnit/JunitXml.TestLogger, go-junit-report, mjs-unit --junit) → 'junit'
11
+ * NUnit 2 et 3 (TestResult.xml) → 'nunit'
12
+ * TRX (dotnet test --logger trx : VSTest, xUnit, MSTest, NUnit) → 'trx'
13
+ * TestNG natif (testng-results.xml) → 'testng'
14
+ * Robot Framework (output.xml, RF 6 et 7) → 'robot'
15
+ * CTest (ctest -T Test → Testing/<horodatage>/Test.xml) → 'ctest'
16
+ * Cucumber JSON (cucumber-js, Cucumber-JVM, SpecFlow/Reqnroll…) → 'cucumber'
17
+ * Behave JSON (behave -f json) → 'behave'
18
+ * Allure 2 (allure-results/*-result.json, un fichier ou un tableau) → 'allure'
19
+ * TAP (node --test, Perl…) → 'tap'
20
+ * CTRF (ctrf-io, mjs-unit --ctrf) → 'ctrf'
21
+ * mjs-unit --json / --compte-rendu / --intoto → 'mjs-unit' / 'compte-rendu' / 'intoto'
22
+ * rapport HTML → 'html' : preuve lisible par un humain, non lue
23
+ *
24
+ * Les formats NUnit à Allure ont été ajoutés le 10/10/2026 à la demande du fondateur : ce sont ceux que lisent Xray,
25
+ * Allure TestOps, TestRail, Zephyr, ReportPortal et Azure DevOps (docs/01c §5 bis). Le rapport du client reste le sien.
26
+ *
27
+ * Le format se reconnaît au CONTENU. Un statut incompris devient `blocked`, jamais `pass`. Pur : aucune E/S, aucune
28
+ * dépendance — `./lecteurs` s'importe partout (navigateur compris).
29
+ */
30
+ export type FormatRapport = 'mjs-unit' | 'compte-rendu' | 'junit' | 'nunit' | 'trx' | 'testng' | 'robot' | 'ctest' | 'cucumber' | 'behave' | 'allure' | 'ctrf' | 'intoto' | 'tap' | 'html' | 'illisible';
31
+ export type StatutLu = 'pass' | 'fail' | 'skipped' | 'blocked';
32
+ /** `tags` : les étiquettes de l'outil (Cucumber @T-MOD-01, Robot, Allure) — `rattacherAuPlan` y cherche une référence du plan. */
33
+ export interface ResultatLu {
34
+ name: string;
35
+ status: StatutLu;
36
+ durationMs?: number;
37
+ error?: string;
38
+ tags?: string[];
39
+ }
40
+ export interface RapportLu {
41
+ title: string;
42
+ generatedAt: string | null;
43
+ suites: {
44
+ file: string;
45
+ results: ResultatLu[];
46
+ }[];
47
+ }
48
+ export interface LectureRapport {
49
+ format: FormatRapport;
50
+ /** la forme que lit importTestRun ; null si le corps n'est pas un rapport d'essais lisible par une machine */
51
+ rapport: RapportLu | null;
52
+ /** l'objet reçu (JSON), tel quel — c'est sur lui que porte l'empreinte d'un compte rendu */
53
+ brut: unknown;
54
+ erreur?: string;
55
+ }
56
+ /** Les formats lus, NOMMÉS dans chaque motif de refus : le client sait quoi envoyer sans chercher. */
57
+ export declare const FORMATS_LUS: {
58
+ readonly xml: "JUnit XML, NUnit 2/3, TRX (VSTest), TestNG, Robot Framework (output.xml), CTest (Test.xml)";
59
+ readonly json: "CTRF, Cucumber JSON, Behave JSON, Allure 2, in-toto test-result, JSON et compte rendu mjs-unit";
60
+ readonly texte: "TAP";
61
+ };
62
+ /** Le format d'un corps de rapport, reconnu à son contenu. */
63
+ export declare function detecterFormat(corps: unknown): FormatRapport;
64
+ /** Lit un rapport d'essais, quel qu'en soit le format ; jamais d'exception : un corps illisible est DIT illisible. */
65
+ export declare function lireRapportEssais(corps: unknown): LectureRapport;
66
+ /** La référence du plan que désigne ce nom d'essai, ou '' : le nom exact d'abord, puis par jetons. */
67
+ export declare function refDuPlan(nom: string, refs: Iterable<string>): string;
68
+ /**
69
+ * Pose sur chaque essai sa référence du plan (`T-MOD-01 — test_T_MOD_01_…`) et le fichier de son `autoRef`
70
+ * (`<fichier>::<ref>`). Un fichier déjà conforme au plan est gardé ; une référence présente dans plusieurs fichiers
71
+ * ne fait rien deviner (le fichier d'origine reste) ; un essai hors plan est laissé tel quel. Pur.
72
+ */
73
+ export declare function rattacherAuPlan<R extends {
74
+ name: string;
75
+ }>(suites: {
76
+ file: string;
77
+ results: R[];
78
+ }[], autoRefs: Iterable<string>): {
79
+ file: string;
80
+ results: R[];
81
+ }[];
82
+ //# sourceMappingURL=lecteurs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lecteurs.d.ts","sourceRoot":"","sources":["../src/lecteurs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,WAAW,CAAC;AACzM,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;AAC/D,kIAAkI;AAClI,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE;AACpH,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,UAAU,EAAE,CAAA;KAAE,EAAE,CAAA;CAAE;AAC3H,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,8GAA8G;IAC9G,OAAO,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B,4FAA4F;IAC5F,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,sGAAsG;AACtG,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAwCX,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAM5D;AAwQD,sHAAsH;AACtH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CA4BhE;AASD,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAWrE;AAcD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,EAAE,CAAA;CAAE,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,EAAE,CAAA;CAAE,EAAE,CAuBlK"}