test(e2e): le parcours d'un primo-arrivant, et un harnais qui échoue au lieu de se pendre
Aucun parcours n'avait jamais marché le chemin d'un nouvel arrivant : tous pré-injectaient l'identifiant dans l'URL, ce qui fait résoudre l'identité sans jamais afficher la barrière. La suite était verte par-dessus un lien de téléchargement pointant sur un fichier que personne ne servait — et le serveur de test répondait la page HTML de l'application pour tout chemin inconnu, donc un fichier manquant ne POUVAIT pas échouer. Le nouveau parcours part d'un profil vide : barrière, téléchargement réel, import dans l'application portefeuille, saisie de l'identifiant, remise au broker, retour dans l'iframe. Il vérifie neuf points, dont celui qui compte — l'identifiant a survécu et l'identité rapportée est celle qui a été saisie. Le harnais, lui, se pendait au lieu d'échouer. Cause observée : le tuyau devtools de Chromium lâche et Playwright n'émet ni close ni disconnected, si bien que la suite bloquait dans son propre nettoyage sans imprimer ni résumé ni l'échec déjà en route. Toutes les attentes sont désormais bornées et nomment ce qu'elles attendaient ; vérifié en cassant délibérément une attente, et observé en conditions réelles — trois minutes et « gave up waiting for: alice to sign in » là où j'ai tué trois exécutions d'une heure ce matin. Deux exécutions simultanées ne se détruisent plus : verrou atomique sur le profil, et récupération d'un navigateur laissé par une exécution tuée. Le marqueur devient .user-consumed — il n'a jamais attesté d'une disponibilité, seulement qu'un lot avait déjà pris l'utilisateur de ce profil. Au passage, la destruction du profil dépendait du marqueur, écrit en FIN de lot : une exécution tuée avant laissait un profil que la suivante réutilisait, et héritait de sa casse. Elle dépend maintenant du profil. La suite applicative reste non mesurée sur cette machine : un conteneur en boucle de redémarrage recycle son interface réseau, et sept exécutions sur dix échouent sur le transport. Trois sont passées 21/21.
This commit is contained in:
@@ -18,6 +18,7 @@ import { ng as realNg, init as realInit } from "@ng-org/web";
|
||||
import {
|
||||
configure,
|
||||
docs,
|
||||
init,
|
||||
subscribeDoc,
|
||||
subscribeDocs,
|
||||
readUnion,
|
||||
@@ -107,6 +108,22 @@ configure({
|
||||
sharedWallet: { fileUrl: "/harness-not-used.ngw", password: "harness" },
|
||||
});
|
||||
|
||||
/**
|
||||
* Who this page opens as — and why it has to open as somebody.
|
||||
*
|
||||
* The boot below goes through the POLYFILL's `init`, not the injected one, so that the
|
||||
* 42 checks run over the ordering an application actually gets: settle the identity, THEN
|
||||
* hand the page to the broker (`surface/lifecycle.ts`). A page with no identity would
|
||||
* raise the barrier instead and never hand over, so the harness supplies one.
|
||||
*
|
||||
* Set BEFORE `configureStoreRegistry` deliberately: until the registry is wired,
|
||||
* `setCurrentUser` fires no connection work (`bootstrap.ts`), so this costs the batch
|
||||
* neither an account nor a broker round-trip. Every check that cares about identity sets
|
||||
* its own anyway — this one is only what the page opened as.
|
||||
*/
|
||||
const BOOT_IDENTITY = "e2e-harness";
|
||||
setCurrentUser(BOOT_IDENTITY);
|
||||
|
||||
configureStoreRegistry({
|
||||
// The registry (+ subscribe/inbox/read-model) reach the session
|
||||
// through this. It resolves once the broker connects.
|
||||
@@ -189,6 +206,16 @@ const identity = new IdentityStore(
|
||||
async accessGateFirstVisit(raw: string) {
|
||||
setCurrentUser(null);
|
||||
try { window.localStorage.removeItem("ng-eventually:identity"); } catch {}
|
||||
// A first visit has no `?ng-id=` either, and the URL is the branch the gate consults
|
||||
// FIRST — so clearing storage alone stopped describing a first visit the moment the
|
||||
// boot started settling an identity (which writes the parameter, as every settling
|
||||
// path must). Leaving it there would make this check pass for the wrong reason on a
|
||||
// gate that had stopped asking at all.
|
||||
try {
|
||||
const withoutIdentity = new URL(window.location.href);
|
||||
withoutIdentity.searchParams.delete("ng-id");
|
||||
window.history.replaceState(null, "", withoutIdentity.toString());
|
||||
} catch {}
|
||||
const done = ensureIdentity();
|
||||
const gate = document.querySelector('[data-ng-eventually="access-gate"]');
|
||||
const root = gate?.shadowRoot ?? null;
|
||||
@@ -851,7 +878,7 @@ const identity = new IdentityStore(
|
||||
// caps/read-filter are EMULATED in-memory (CapRegistry) — the real broker does
|
||||
// NOT yet enforce per-doc read caps here (one shared wallet reads everything).
|
||||
// We test what the SDK enforces: the in-memory read-filtered VIEW, which after
|
||||
// P1a is KEY POSSESSION — you read what your keyring holds, nothing else.
|
||||
// cap-surface is KEY POSSESSION — you read what your keyring holds, nothing else.
|
||||
capsReadFilter() {
|
||||
resetCaps();
|
||||
injectedSetItems = [
|
||||
@@ -1028,19 +1055,26 @@ const identity = new IdentityStore(
|
||||
// ── Connect to the real broker ─────────────────────────────────────────────
|
||||
// Mirrors ngSession.ts: register the init callback; the broker (this iframe is
|
||||
// loaded by it) drives the connection and calls back with the session.
|
||||
//
|
||||
// Through the POLYFILL's `init`, not the injected `realInit` — even though `realInit` is
|
||||
// what ends up being called (it is what `configure` injects, above). Calling it directly
|
||||
// skipped the forwarder that settles the identity before delegating, so the 42 checks ran
|
||||
// over an ordering no application has, and the defect that ordering exists to prevent —
|
||||
// the hand-over happening before the identity reaches the address bar — could not have
|
||||
// been caught here. What an application writes is this line.
|
||||
(async () => {
|
||||
try {
|
||||
await (realInit as any)(
|
||||
(event: any) => {
|
||||
session = event.session as BrokerSession;
|
||||
await init(
|
||||
(event: { session: BrokerSession }) => {
|
||||
session = event.session;
|
||||
state.status = "connected";
|
||||
sessionResolve(session);
|
||||
},
|
||||
true,
|
||||
[],
|
||||
);
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
state.status = "error";
|
||||
state.error = String(e?.message ?? e);
|
||||
state.error = e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user