test(@wip): reconnexion même identité — documente le défaut de relecture à froid
Scénario @data @wip (exclu du run par défaut) qui documente un défaut RÉEL non encore corrigé : une PAGE FRAÎCHE pour la MÊME identité, sur le même wallet persistant (nouveau login broker → session verifier fraîche), relit VIDE ses propres données (accueil vide, isParticipating=false, count=0). Mesuré au niveau app sur broker réel. Le fix lib `open-repo` (branche fix/session-rehydration-on-login, non mergée) fait remonter le PROTECTED (participation) au cold-start mais PAS l'accueil PUBLIC : `readScopeIndex` de l'index de scope public rend 0 — observé même côté écrivain même-session — alors que le code d'index de la lib est prouvé scope-symétrique. Cause exacte encore à mesurer sous broker (l'hypothèse « mauvais graphe » est déjà réfutée en amont). Reste @wip tant que le fix n'est pas complet et validé. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# language: fr
|
||||
@EVENT @priority-1 @data @wip
|
||||
Fonctionnalité: Reconnexion d'une même identité sur le wallet persistant
|
||||
En tant qu'utilisateur qui, sur le MÊME wallet physique, ouvre une PAGE FRAÎCHE
|
||||
(nouveau login broker, session fraîche) sous le MÊME identifiant qu'avant,
|
||||
Je dois relire MES PROPRES données (mon événement, ma participation)
|
||||
Afin que rien ne disparaisse à la reconnexion.
|
||||
|
||||
# Régression de RECONNEXION (distincte de l'isolation deux-identités). @wip : le
|
||||
# défaut est RÉEL mais non encore corrigé — ce scénario le documente et échoue
|
||||
# tant que le fix n'est pas complet.
|
||||
#
|
||||
# SYMPTÔME observé (broker réel, mesuré) : une identité A crée E + s'y inscrit
|
||||
# sur la page principale ; une PAGE FRAÎCHE pour la MÊME identité A (même wallet,
|
||||
# nouveau login → session verifier fraîche) relit VIDE (home=[],
|
||||
# isParticipating=false, count=0). La page fraîche DOIT retrouver E, sa
|
||||
# participation, et un count autoritatif de 1.
|
||||
#
|
||||
# MÉCANISME (sous investigation, à confirmer sous broker) : la lecture ancrée à
|
||||
# froid tape des repos pas encore ouverts. Le fix lib `open-repo` fait remonter
|
||||
# le PROTECTED (participation) au cold-start, mais PAS l'accueil PUBLIC :
|
||||
# `readScopeIndex` de l'index de scope public rend 0 (observé même côté écrivain
|
||||
# même-session) alors que le code d'index de la lib est scope-symétrique — cause
|
||||
# exacte encore à mesurer (broker requis).
|
||||
|
||||
@data
|
||||
Scénario: Une page fraîche pour la même identité relit ses propres données
|
||||
Étant donné que l'identité A crée l'événement "Événement de reconnexion de A" et s'y inscrit
|
||||
Quand une page fraîche pour la MÊME identité A recharge sur le même wallet
|
||||
Alors l'événement "Événement de reconnexion de A" est sur l'accueil de la page fraîche A
|
||||
Et la page fraîche A est participante de l'événement "Événement de reconnexion de A"
|
||||
Et le compte autoritatif de participation de A à l'événement "Événement de reconnexion de A" est 1
|
||||
@@ -0,0 +1,84 @@
|
||||
import { When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from 'chai';
|
||||
import type { FestipodWorld } from '../../../../shared/support/world';
|
||||
import { pool } from '../../../../shared/support/browserPool';
|
||||
|
||||
// RECONNECTION of the SAME identity on the SAME persistent wallet (@data, real
|
||||
// broker). Distinct from the two-identity isolation scenario: here the fresh page
|
||||
// re-enters under the SAME identifier A (same virtual wallet), on a NEW broker
|
||||
// login (fresh verifier session). The defect under test: the fresh page reads its
|
||||
// OWN data EMPTY (home=[], isParticipating=false, authCount=0) because the anchored
|
||||
// listing path (listMyEntityDocs → readScopeIndex, then readUnion/readDoc) queries
|
||||
// repos not yet in `self.repos` at cold-start and silently returns 0 rows.
|
||||
//
|
||||
// Identity A is the scenario's fresh virtual-wallet username (this.freshUser, set
|
||||
// by the Before hook into localStorage on every origin). A creates E via the REAL
|
||||
// app path (createEventReal) and joins it (appJoinEvent) on the MAIN page. Then a
|
||||
// FRESH PAGE is brought up on the SAME persistent wallet context with the SAME
|
||||
// identifier A in localStorage BEFORE any script — the closest analogue to the real
|
||||
// app's re-enter-gate / reload path (a brand-new NgDataProvider mount + a fresh
|
||||
// broker session that must re-open A's own repos). Montage identical to
|
||||
// isolation.steps.ts, except the fresh page reuses this.freshUser (SAME A) rather
|
||||
// than minting a new identifier B.
|
||||
|
||||
// The Given "l'identité A crée l'événement {string} et s'y inscrit" is REUSED from
|
||||
// isolation.steps.ts (same wording, same behavior — A creates E and joins on the
|
||||
// main page). It stores this.isoEventId / this.isoAId, which the steps below read.
|
||||
|
||||
When('une page fraîche pour la MÊME identité A recharge sur le même wallet', { timeout: 120000 }, async function (this: FestipodWorld) {
|
||||
// SAME identity A as the main page: reuse the scenario's fresh virtual-wallet
|
||||
// username (set by the Before hook). NOT a new identifier — this is a reconnect,
|
||||
// not an identity switch.
|
||||
const aIdentifier = (this as any).freshUser as string;
|
||||
const ctx = this.page!.context();
|
||||
const freshPage = await ctx.newPage();
|
||||
await freshPage.addInitScript((u: string) => {
|
||||
try { window.localStorage.setItem('festipod.account.username', u); } catch { /* opaque */ }
|
||||
}, aIdentifier);
|
||||
await freshPage.addInitScript(() => {
|
||||
(globalThis as Record<string, unknown>).__FESTIPOD_ACCESS_GATE_DISABLED__ = true;
|
||||
});
|
||||
freshPage.on('console', (msg) => { if (msg.type() === 'error') console.error('[FreshApage console]', msg.text()); });
|
||||
// New broker login → fresh verifier session on the SAME persistent wallet.
|
||||
const freshFrame = await pool.setupBrokerPage!(freshPage, pool.harnessUrl!);
|
||||
await freshFrame.waitForFunction(() => (window as any).__testData?.ready === true, { timeout: 60000 });
|
||||
// Let A's listing effect + anchored union read run on the fresh session (this is
|
||||
// exactly the cold-start read path the fix heals).
|
||||
await freshFrame.evaluate(async () => {
|
||||
const td = (window as any).__testData;
|
||||
await td.ensureCurrentUser();
|
||||
await new Promise(r => setTimeout(r, 6000));
|
||||
});
|
||||
(this as any).recoFreshFrame = freshFrame;
|
||||
});
|
||||
|
||||
Then('l\'événement {string} est sur l\'accueil de la page fraîche A', async function (this: FestipodWorld, title: string) {
|
||||
const freshFrame = (this as any).recoFreshFrame;
|
||||
const onHome = await freshFrame.evaluate((title: string) => {
|
||||
const td = (window as any).__testData;
|
||||
return td.homeEventTitles().includes(title);
|
||||
}, title);
|
||||
expect(onHome, `"${title}" MUST appear on the fresh A page's home (getUserEvents(A) after reconnect)`).to.be.true;
|
||||
});
|
||||
|
||||
Then('la page fraîche A est participante de l\'événement {string}', async function (this: FestipodWorld, title: string) {
|
||||
const freshFrame = (this as any).recoFreshFrame;
|
||||
const eventId = (this as any).isoEventId;
|
||||
const isPart = await freshFrame.evaluate(async ({ eventId }: { eventId: string }) => {
|
||||
const td = (window as any).__testData;
|
||||
const aId = await td.ensureCurrentUser();
|
||||
return td.isParticipating(eventId, aId);
|
||||
}, { eventId });
|
||||
expect(isPart, `The fresh A page MUST read its own participation in "${title}" after reconnect`).to.be.true;
|
||||
});
|
||||
|
||||
Then('le compte autoritatif de participation de A à l\'événement {string} est {int}', { timeout: 60000 }, async function (this: FestipodWorld, _title: string, expected: number) {
|
||||
const freshFrame = (this as any).recoFreshFrame;
|
||||
const eventId = (this as any).isoEventId;
|
||||
const count = await freshFrame.evaluate(async ({ eventId }: { eventId: string }) => {
|
||||
const td = (window as any).__testData;
|
||||
const aId = await td.ensureCurrentUser();
|
||||
return td.authParticipationCount(eventId, aId);
|
||||
}, { eventId });
|
||||
expect(count, `Authoritative broker count of A's participation must be ${expected} after reconnect`).to.equal(expected);
|
||||
});
|
||||
Reference in New Issue
Block a user