Ng eventually #1
+121
-28
@@ -1,23 +1,26 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* validate.ts — Full validation matrix (broker-level tests only, no @ui/@e2e).
|
||||
* validate.ts — Validation matrix (broker-level tests only, no @ui/@e2e).
|
||||
*
|
||||
* Runs in sequence:
|
||||
* Default run (no flags) — key subset only, fast:
|
||||
* (a) Polyfill unit tests (@ng-eventually/client — bun test)
|
||||
* (b) Polyfill e2e real-broker (@ng-eventually/client — bun run test:e2e)
|
||||
* (c) Festipod @data (cucumber --tags @data)
|
||||
* (b) Polyfill e2e real-broker (@ng-eventually/client — bun run e2e/run.ts)
|
||||
* (c) Festipod @data KEY SUBSET (cucumber --name regex covering terrain bugs)
|
||||
* (d) Festipod @multibrowser (cucumber --tags @multibrowser)
|
||||
* (e) Festipod @wip [informational only, non-blocking]
|
||||
*
|
||||
* With --full flag:
|
||||
* (c) becomes full @data suite (cucumber --tags @data)
|
||||
*
|
||||
* Each step runs even if the previous one failed (--bail mode is OFF).
|
||||
* Exit code is non-zero if any non-informational step has failures.
|
||||
*
|
||||
* Profile rotation: both Playwright profiles are rotated before the run
|
||||
* when their size exceeds BLOAT_THRESHOLD_MB (default 50 MB), to avoid
|
||||
* the sparql_query hang described in caveat_wallet-bloat-hang.
|
||||
* Profile rotation: both Playwright profiles are rotated before @data and
|
||||
* @multibrowser when their size exceeds BLOAT_THRESHOLD_MB (default 50 MB),
|
||||
* to avoid the sparql_query hang described in caveat_wallet-bloat-hang.
|
||||
*/
|
||||
|
||||
import { spawnSync, execSync } from "child_process";
|
||||
import { spawnSync } from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
@@ -37,8 +40,41 @@ const POLYFILL_PROFILE = path.join(
|
||||
/** Rotate profile when it exceeds this many MB (caveat_wallet-bloat-hang). */
|
||||
const BLOAT_THRESHOLD_MB = 50;
|
||||
|
||||
/** Per-step timeout in ms — generous for broker + headless wallet creation. */
|
||||
const STEP_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
||||
/**
|
||||
* Per-step timeouts:
|
||||
* - polyfill unit/e2e: short steps, keep 10 min
|
||||
* - @data key subset: generous — BeforeAll + 8 scenarios, ~15 min margin
|
||||
* - @data full: full suite, ~35 min margin
|
||||
* - @multibrowser: 7 scenarios, ~10 min margin
|
||||
* - @wip: informational, 10 min
|
||||
*/
|
||||
const TIMEOUT_POLYFILL_UNIT_MS = 10 * 60 * 1000; // 10 min
|
||||
const TIMEOUT_POLYFILL_E2E_MS = 10 * 60 * 1000; // 10 min
|
||||
const TIMEOUT_DATA_KEY_MS = 15 * 60 * 1000; // 15 min (key subset)
|
||||
const TIMEOUT_DATA_FULL_MS = 35 * 60 * 1000; // 35 min (--full)
|
||||
const TIMEOUT_MULTIBROWSER_MS = 10 * 60 * 1000; // 10 min
|
||||
const TIMEOUT_WIP_MS = 10 * 60 * 1000; // 10 min
|
||||
|
||||
/**
|
||||
* Key-subset --name regex: matches exactly the 8 scenarios that cover the
|
||||
* known terrain bugs (inscription, désinscription, isolation, reconnexion,
|
||||
* compteur dérivé, créateur ne participe pas, auth vide, auth distinctes).
|
||||
*
|
||||
* Uses a single cucumber invocation so BeforeAll (broker login) runs once.
|
||||
*
|
||||
* French accent chars must be URL-safe in the regex — cucumber uses JS
|
||||
* RegExp, which handles unicode natively; we pass the literal string.
|
||||
*/
|
||||
const DATA_KEY_NAME_REGEX = [
|
||||
"S'inscrire à un événement",
|
||||
"Se désinscrire d'un événement$",
|
||||
"Une identité fraîche ne voit pas la participation d'une autre",
|
||||
"Une page fraîche pour la même identité relit ses propres données",
|
||||
"Le créateur ne participe pas automatiquement à son événement",
|
||||
"L'inscription fait converger le compteur dérivé du propriétaire",
|
||||
"Un portefeuille connecté est vide par défaut",
|
||||
"Les données du portefeuille sont distinctes des données par défaut",
|
||||
].join("|");
|
||||
|
||||
// ─── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -102,12 +138,14 @@ function runStep(
|
||||
cmd: string,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
timeoutMs: number,
|
||||
extraEnv: Record<string, string> = {},
|
||||
): StepResult {
|
||||
const t0 = Date.now();
|
||||
console.log(`\n${"═".repeat(60)}`);
|
||||
console.log(`▶ ${label}`);
|
||||
console.log(` ${cmd} ${args.join(" ")} (cwd: ${cwd})`);
|
||||
console.log(` timeout: ${Math.round(timeoutMs / 60000)}min`);
|
||||
console.log(`${"═".repeat(60)}`);
|
||||
|
||||
const env = { ...process.env, ...extraEnv };
|
||||
@@ -116,7 +154,7 @@ function runStep(
|
||||
cwd,
|
||||
env,
|
||||
encoding: "utf-8",
|
||||
timeout: STEP_TIMEOUT_MS,
|
||||
timeout: timeoutMs,
|
||||
maxBuffer: 20 * 1024 * 1024, // 20MB
|
||||
});
|
||||
|
||||
@@ -197,7 +235,7 @@ function extractFailures(output: string, label: string): string[] {
|
||||
}
|
||||
// Cucumber "N scenarios (M failed)" summary
|
||||
const summary = lines.find((l) =>
|
||||
/\d+ scenarios?.*(failed|undefined)/.test(l),
|
||||
/\d+ sc[eé]narios?.*(failed|undefined)/.test(l),
|
||||
);
|
||||
if (summary) failures.push(summary.trim());
|
||||
// Individual scenario fail lines: "✗ Scenario name (features/...)"
|
||||
@@ -222,10 +260,16 @@ function fmtDuration(ms: number): string {
|
||||
function printMatrix(
|
||||
steps: StepResult[],
|
||||
wipResult: StepResult | null,
|
||||
fullMode: boolean,
|
||||
): void {
|
||||
console.log("\n");
|
||||
console.log("╔══════════════════════════════════════════════════════════╗");
|
||||
console.log("║ VALIDATION MATRIX ║");
|
||||
if (fullMode) {
|
||||
console.log("║ (mode: --full, @data complet) ║");
|
||||
} else {
|
||||
console.log("║ (mode: défaut, sous-ensemble clé) ║");
|
||||
}
|
||||
console.log("╚══════════════════════════════════════════════════════════╝");
|
||||
console.log("");
|
||||
|
||||
@@ -267,12 +311,15 @@ function printMatrix(
|
||||
console.log(` 🔴 ${failed.length} STEP(S) FAILED: ${failed.map((s) => s.label).join(", ")}`);
|
||||
}
|
||||
console.log(` ⏱ Total: ${fmtDuration(totalMs)}`);
|
||||
if (!fullMode) {
|
||||
console.log(" ℹ️ Pour @data complet : bun run validate -- --full");
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
// ─── Cucumber command builder ───────────────────────────────────────────────
|
||||
|
||||
function cucumberArgs(tags: string): string[] {
|
||||
function cucumberArgsByTags(tags: string): string[] {
|
||||
return [
|
||||
"--import",
|
||||
"tsx/esm",
|
||||
@@ -284,16 +331,38 @@ function cucumberArgs(tags: string): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
function cucumberArgsByName(nameRegex: string): string[] {
|
||||
return [
|
||||
"--import",
|
||||
"tsx/esm",
|
||||
"node_modules/.bin/cucumber-js",
|
||||
"--config",
|
||||
"cucumber.json",
|
||||
"--tags",
|
||||
"@data",
|
||||
"--name",
|
||||
nameRegex,
|
||||
];
|
||||
}
|
||||
|
||||
// ─── Main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async function main(): Promise<void> {
|
||||
console.log("🔍 Festipod — Full Validation Run");
|
||||
const args = process.argv.slice(2);
|
||||
const fullMode = args.includes("--full");
|
||||
|
||||
if (fullMode) {
|
||||
console.log("🔍 Festipod — Full Validation Run (--full : @data complet)");
|
||||
} else {
|
||||
console.log("🔍 Festipod — Validation Run (sous-ensemble clé)");
|
||||
console.log(" Pour @data complet : bun run validate -- --full");
|
||||
}
|
||||
console.log(` Festipod: ${FESTIPOD_DIR}`);
|
||||
console.log(` Polyfill: ${POLYFILL_DIR}`);
|
||||
console.log("");
|
||||
|
||||
// ── Profile rotation ─────────────────────────────────────────────────────
|
||||
console.log("── Profile rotation check ──────────────────────────────────");
|
||||
// ── Profile rotation AVANT les étapes broker ──────────────────────────────
|
||||
console.log("── Profile rotation check (avant @data et @multibrowser) ────");
|
||||
rotateProfile(FESTIPOD_PROFILE, "festipod");
|
||||
rotateProfile(POLYFILL_PROFILE, "polyfill-lib");
|
||||
|
||||
@@ -301,7 +370,13 @@ async function main(): Promise<void> {
|
||||
|
||||
// ── (a) Polyfill unit tests ───────────────────────────────────────────────
|
||||
steps.push(
|
||||
runStep("polyfill:unit", "bun", ["test"], POLYFILL_DIR),
|
||||
runStep(
|
||||
"polyfill:unit",
|
||||
"bun",
|
||||
["test"],
|
||||
POLYFILL_DIR,
|
||||
TIMEOUT_POLYFILL_UNIT_MS,
|
||||
),
|
||||
);
|
||||
|
||||
// ── (b) Polyfill e2e real broker ──────────────────────────────────────────
|
||||
@@ -311,26 +386,43 @@ async function main(): Promise<void> {
|
||||
"bun",
|
||||
["run", "e2e/run.ts"],
|
||||
POLYFILL_DIR,
|
||||
TIMEOUT_POLYFILL_E2E_MS,
|
||||
),
|
||||
);
|
||||
|
||||
// ── (c) Festipod @data ────────────────────────────────────────────────────
|
||||
steps.push(
|
||||
runStep(
|
||||
"festipod:@data",
|
||||
"node",
|
||||
cucumberArgs("@data"),
|
||||
FESTIPOD_DIR,
|
||||
),
|
||||
);
|
||||
if (fullMode) {
|
||||
// --full : lance tout @data
|
||||
steps.push(
|
||||
runStep(
|
||||
"festipod:@data (complet)",
|
||||
"node",
|
||||
cucumberArgsByTags("@data"),
|
||||
FESTIPOD_DIR,
|
||||
TIMEOUT_DATA_FULL_MS,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// défaut : sous-ensemble clé en UNE invocation (BeforeAll partagé)
|
||||
steps.push(
|
||||
runStep(
|
||||
"festipod:@data (clé)",
|
||||
"node",
|
||||
cucumberArgsByName(DATA_KEY_NAME_REGEX),
|
||||
FESTIPOD_DIR,
|
||||
TIMEOUT_DATA_KEY_MS,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── (d) Festipod @multibrowser ────────────────────────────────────────────
|
||||
steps.push(
|
||||
runStep(
|
||||
"festipod:@multibrowser",
|
||||
"node",
|
||||
cucumberArgs("@multibrowser"),
|
||||
cucumberArgsByTags("@multibrowser"),
|
||||
FESTIPOD_DIR,
|
||||
TIMEOUT_MULTIBROWSER_MS,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -339,12 +431,13 @@ async function main(): Promise<void> {
|
||||
const wipResult = runStep(
|
||||
"festipod:@wip",
|
||||
"node",
|
||||
cucumberArgs("@wip"),
|
||||
cucumberArgsByTags("@wip"),
|
||||
FESTIPOD_DIR,
|
||||
TIMEOUT_WIP_MS,
|
||||
);
|
||||
|
||||
// ── Matrix ────────────────────────────────────────────────────────────────
|
||||
printMatrix(steps, wipResult);
|
||||
printMatrix(steps, wipResult, fullMode);
|
||||
|
||||
// ── Exit code ─────────────────────────────────────────────────────────────
|
||||
const anyFailed = steps.some((s) => s.status !== "passed");
|
||||
|
||||
Reference in New Issue
Block a user