Ng eventually #1
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Couche @data — Playwright pilote Chromium (profil persistant) qui s'authentifie au broker NextGraph réel chargeant harness-ng.tsx en iframe ; cycle de vie wallet automatisé (création + login bootstrap), bridge window.__testData, fallback mock
|
||||
last_checked: 2026-07-03
|
||||
last_checked: 2026-07-05
|
||||
---
|
||||
|
||||
# Couche `@data` (broker réel)
|
||||
@@ -50,9 +50,17 @@ Cucumber → Playwright (Chromium, profil persistant)
|
||||
`ensureCurrentUser()` avant `joinEvent` (sinon participation écrite sans user → jetée en
|
||||
lecture, ne fait jamais l'aller-retour) et attendent (`waitForFunction`) que la participation
|
||||
soit relue.
|
||||
- **Caveat wallet persistant** : le wallet partagé **accumule** les docs per-entité à chaque run
|
||||
(seed + inscriptions). Le fan-out de lecture (`listEntityDocs`) parcourt tous les docs de tous
|
||||
les comptes → ralentit et fait *timeouter* les steps quand le wallet est pollué. Pour une suite
|
||||
fiable, repartir d'un wallet **frais** (supprimer `.playwright-profile/` → recréation
|
||||
automatique) ; le seed connecté est volontairement **allégé** (peu de docs) car chaque
|
||||
`docCreate` est un aller-retour broker sériel ~2s.
|
||||
- **Caveat wallet persistant + isolation par scénario (T03.j)** : le wallet partagé **accumule**
|
||||
le registre de comptes émulé et les docs per-entité à chaque scénario/run. Le fan-out de lecture
|
||||
(`listEntityDocs` = `allAccounts()` → 1 SELECT/compte) parcourt tous les docs de tous les comptes
|
||||
→ ralentit et fait *timeouter* les steps quand le wallet est pollué. Ce registre vit **côté
|
||||
broker** : supprimer `.playwright-profile/` ne le nettoie PAS (re-sync depuis le broker) et force
|
||||
une re-auth lente — mauvais levier. À la place, le `Before` @data appelle
|
||||
`window.__testData.resetDataState()` : **UN** SPARQL DELETE sur le graphe ancre (private-store)
|
||||
qui efface tous les records `urn:ng-eventually:shim:Account` → `allAccounts()` s'effondre à vide →
|
||||
le fan-out se **borne** à ce que le scénario courant reprovisionne (comptes recréés paresseusement
|
||||
par `ensureAccount`). O(1) sur UN graphe — **pas** un delete en fan-out (qui saturait le navigateur,
|
||||
cf. T03.i `authClearParticipation` retiré). Borné à ≤10s (`Promise.race`) pour ne pas disputer le
|
||||
budget 60s du `Before` (login broker déjà lent). Infra de test uniquement — ne touche ni la lib ni
|
||||
le modèle produit ni le chemin de lecture applicatif. Le seed connecté reste **allégé** (peu de
|
||||
docs) car chaque `docCreate` est un aller-retour broker sériel ~2s.
|
||||
|
||||
@@ -578,6 +578,26 @@ Before({ timeout: 60000 }, async function (this: FestipodWorld, scenario) {
|
||||
() => (window as any).__testData?.ready === true,
|
||||
{ timeout: 30000 },
|
||||
);
|
||||
|
||||
// PER-SCENARIO STATE ISOLATION (T03.j). The @data suite shares ONE
|
||||
// persistent broker-backed wallet, so the emulated account registry
|
||||
// ACCUMULATES every account any prior scenario/run created — growing the
|
||||
// read fan-out (`allAccounts()` → per-account `listEntityDocs`) until it
|
||||
// gets slow and flaky. Purge the registry anchor once here so each @data
|
||||
// scenario starts from a CLEAN registry and the fan-out stays bounded to
|
||||
// what this scenario re-provisions. A single SPARQL DELETE on ONE graph —
|
||||
// not a fan-out delete.
|
||||
// HARD-BOUNDED (≤10s): this reset shares the Before hook's 60s budget with
|
||||
// the (already slow, intermittent) broker login. It must NEVER contend for
|
||||
// that budget — a slow purge on a hugely-accumulated anchor graph, or a
|
||||
// broker stall, is swallowed and the scenario proceeds (its own steps still
|
||||
// gate on state). So race it against a 10s cap and never let it throw.
|
||||
await Promise.race([
|
||||
this.appFrame.evaluate(async () => {
|
||||
try { await (window as any).__testData?.resetDataState?.(); } catch { /* best-effort */ }
|
||||
}),
|
||||
new Promise((r) => setTimeout(r, 10000)),
|
||||
]).catch(() => { /* best-effort */ });
|
||||
} else {
|
||||
// Mock mode: load harness directly
|
||||
await this.page!.setContent('<!DOCTYPE html><html><body><div id="root"></div></body></html>');
|
||||
|
||||
@@ -366,6 +366,55 @@ function ConnectedHarness() {
|
||||
return { cleared: all.length };
|
||||
},
|
||||
|
||||
/**
|
||||
* PER-SCENARIO STATE ISOLATION (T03.j). The @data suite runs against ONE
|
||||
* persistent broker-backed wallet, so the emulated account registry (the
|
||||
* `urn:ng-eventually:shim:Account` triples in the private-store anchor
|
||||
* graph) ACCUMULATES every account any scenario/run ever provisioned. The
|
||||
* read path is a fan-out: `allAccounts()` → one SPARQL SELECT per account
|
||||
* for `listEntityDocs`. As the registry grows unbounded across runs, that
|
||||
* fan-out gets slow and flaky (same class as the T03.d Chromium saturation).
|
||||
*
|
||||
* This gives each @data scenario a CLEAN registry: a SINGLE SPARQL DELETE
|
||||
* on the ONE private-store anchor graph removes every accumulated Account
|
||||
* record, so `allAccounts()` collapses to empty and the fan-out is bounded
|
||||
* to whatever the CURRENT scenario re-provisions (accounts are lazily
|
||||
* re-created by `ensureAccount` on first use). It is O(1) on ONE graph — NOT
|
||||
* a fan-out delete (which saturated the browser before, see T03.i's removed
|
||||
* `authClearParticipation`). Orphaned per-entity docs are simply never
|
||||
* enumerated once their owning Account record is gone.
|
||||
*
|
||||
* Test-infra ONLY: touches the emulation's registry anchor, never the
|
||||
* product model, the app read path, or the boundary. The lib is untouched;
|
||||
* this reuses the same anchor NURI (`did:ng:${private_store_id}`) and shim
|
||||
* vocabulary the lib's `loadShim`/`ensureAccount` use.
|
||||
*/
|
||||
async resetDataState() {
|
||||
const reg = await import('../utils/storeRegistry');
|
||||
const priv = `did:ng:${session.private_store_id}`;
|
||||
const SHIM = 'urn:ng-eventually:shim';
|
||||
const t0 = Date.now();
|
||||
// Delete every Account record (and its username/doc* predicates) from the
|
||||
// anchor graph. `?p ?o` with the `a shim:Account` guard scopes the delete
|
||||
// strictly to registry triples, leaving anything else in the private
|
||||
// store intact.
|
||||
const del = `
|
||||
DELETE { GRAPH <${priv}> { ?acc ?p ?o } }
|
||||
WHERE {
|
||||
GRAPH <${priv}> {
|
||||
?acc a <${SHIM}:Account> ;
|
||||
?p ?o .
|
||||
}
|
||||
}`;
|
||||
try {
|
||||
await docs.sparqlUpdate(session.session_id, del, priv);
|
||||
} catch { /* best-effort — a broker flake must not fail the scenario */ }
|
||||
// Drop the in-memory account cache so the next registry call re-reads the
|
||||
// now-empty anchor (else a stale cache would keep the old accounts alive).
|
||||
reg.resetRegistryCache();
|
||||
return { resetMs: Date.now() - t0 };
|
||||
},
|
||||
|
||||
/** ONE-TIME CLEANUP (T03.i): the private store accumulated thousands of
|
||||
* historical inbox-deposit triples across test runs (the old inbox anchor
|
||||
* = private store), making `loadShim` a 60s+ full-graph scan. Delete every
|
||||
|
||||
Reference in New Issue
Block a user