@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 +48 -0
- package/bin/cli.mjs +63 -10
- package/bin/mjs-unit-menu.sh +17 -7
- package/dist/lecteurs.d.ts +82 -0
- package/dist/lecteurs.d.ts.map +1 -0
- package/dist/lecteurs.js +518 -0
- package/dist/lecteurs.js.map +1 -0
- package/dist/qatrax.d.ts +76 -0
- package/dist/qatrax.d.ts.map +1 -0
- package/dist/qatrax.js +272 -0
- package/dist/qatrax.js.map +1 -0
- package/dist/report.d.ts +10 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +15 -4
- package/dist/report.js.map +1 -1
- package/llms.txt +8 -0
- package/package.json +24 -3
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
|
-
|
|
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_ = {};
|
package/bin/mjs-unit-menu.sh
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# mjs-unit-menu — le cycle de validation en un menu (0.
|
|
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
|
-
#
|
|
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),
|
|
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"
|
|
42
|
-
|
|
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"}
|