One sign-in drive for every e2e, the public surface and visibility in the rights matrix, and the stack gates in validate

e2e-login-flow and e2e-revocation now share the matrix's driveLogin, which counts a sign-in only once the app is really reached and the app install is asserted; their own loops could report "shell reached" while still on the picker page. The rights matrix gains a public-surface group (allow-listed endpoints answer 200, non-public paths 404, status codes only) and a visibility group (an event with a meeting point and a participant switched public and private by its owner: event, meeting point, attendees collection and catalog listing follow). validate.ts probes the stack and runs test:e2e-rights and test:e2e-revocation as layers, reported SKIPPED when the stack is down and never counted as passed.
This commit is contained in:
Sylvain Duchesne
2026-10-13 18:11:34 +02:00
parent 88f23b7023
commit 9381c206df
5 changed files with 437 additions and 179 deletions
+15 -53
View File
@@ -24,7 +24,11 @@
* nothing with that title reaches the pod).
* 3. RECOVERED — signing in again from the picker goes through
* `registerApp` → the consent screen; accepting it brings the user back to a
* connected app whose probe reads current again, and a write lands.
* connected app whose probe reads current again, and a write lands. The
* pod's own session survived the app's logout, so this sign-in shows no pod
* login form: the sign-in click leads straight to consent.
*
* Both sign-ins are the shared drive `driveLogin` (`e2e-rights-matrix-support.ts`).
*
* Nothing is stubbed: the probe result is whatever the pod answers. The probe
* is triggered only through the app's real paths (page load, window focus,
@@ -45,6 +49,7 @@ import {
type ApodsSession,
type ApodsStackConfig,
} from '../src/shared/support/activitypodsStack';
import { driveLogin, type DriveContext } from './e2e-rights-matrix-support';
const strip = (u: string): string => u.replace(/\/+$/, '');
const BACKEND: string = strip(process.env.BACKEND_URL || 'http://localhost:3003');
@@ -71,7 +76,8 @@ const errors4xx: HttpError[] = [];
const writes: Array<{ phase: Phase; method: string; url: string; status: number }> = [];
const consoleErrors: string[] = [];
const redirects: string[] = [];
const hops: string[] = [];
/** The shared sign-in drive's context (`driveLogin`): it writes the observed hops, tagged by phase; errors are collected by `wire` below. */
const drive: DriveContext = { app: APP, podFront: POD_FRONT, hops: [], errors: [], consoleErrors: [], pageErrors: [], deadline: DEADLINE };
const clean = (s: string): string => s.replace('http://localhost:', ':').replace('http://127.0.0.1:', ':127.');
const bodyText = (page: Page): Promise<string> =>
@@ -123,48 +129,6 @@ const whereIs = async (page: Page): Promise<string> =>
? (page.url().startsWith(`${POD_FRONT}/authorize`) ? 'consent' : `elsewhere:${clean(page.url())}`)
: (await pickerShown(page)) ? 'picker' : 'still-connected';
/** Drive pod login → consent until the connected app shell is reached. */
async function driveToApp(page: Page): Promise<boolean> {
let authorizeSeen = 0;
for (let i = 0; i < 25 && Date.now() < DEADLINE; i++) {
const url = page.url();
if (hops[hops.length - 1] !== `[${phase}] ${clean(url)}`) hops.push(`[${phase}] ${clean(url)}`);
if (url.startsWith(POD_FRONT) && (await page.locator('input[name="username"]').count().catch(() => 0))) {
hops.push(` [${phase}] [login] submitting credentials`);
await page.locator('input[name="username"]').fill(USER).catch(() => {});
await page.locator('input[name="password"]').fill(PASS).catch(() => {});
const btn = page.getByRole('button', { name: /SE CONNECTER|Se connecter|Sign in|Connexion/i });
if (await btn.count()) await btn.first().click().catch(() => {});
else await page.locator('button[type=submit]').first().click().catch(() => {});
await page.waitForTimeout(3500);
continue;
}
if (url.startsWith(`${POD_FRONT}/authorize`)) {
authorizeSeen++;
const acc = page.getByRole('button', { name: /^ACCEPTER$|^Accepter$|^Accept$/i });
const visible = await acc.first().isVisible({ timeout: 5000 }).catch(() => false);
hops.push(` [${phase}] [authorize #${authorizeSeen}] accept visible=${visible}`);
await snap(page, `revocation-${phase}-consent`);
if (visible) await acc.first().click().catch(() => {});
if (authorizeSeen >= 3) { hops.push(` [${phase}] [LOOP] authorize revisited 3x`); return false; }
await page.waitForTimeout(5000);
continue;
}
if (onApp(page)) {
if (url.includes('/login')) { await page.waitForTimeout(3000); continue; }
if (!(await pickerShown(page)) && !(await page.locator('#auth-reauthorizing').count().catch(() => 0))) {
hops.push(` [${phase}] [app] shell reached`);
return true;
}
}
await page.waitForTimeout(1500);
}
return false;
}
/** The event-creation drive from `e2e-login-flow.ts`, reporting what the app showed. */
async function createEvent(page: Page, title: string): Promise<{ eventUri?: string; successToast: boolean; finalUrl: string }> {
let successToast = false;
@@ -271,10 +235,9 @@ function benign(e: HttpError): boolean {
wire(page);
// ── Phase 1: CURRENT ────────────────────────────────────────────────────
await page.goto(`${APP}/`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1500);
await page.getByRole('button', { name: /Se connecter avec localhost:3000/i }).click();
const connected = await driveToApp(page);
// The shared drive opens the app, clicks the pod's sign-in button and follows
// the observed hops (pod login → consent → shell, bottom navigation shown).
const connected = (await driveLogin(page, 'current', USER, PASS, drive)).ok;
const probeRanCurrent = connected && await waitFor(
() => statusSeen.some(s => s.phase === 'current' && s.status === 200 && isCurrent(s.body))
&& probeLogs.some(l => l.phase === 'current' && /probe \(load\) → current/.test(l.text)),
@@ -345,11 +308,10 @@ function benign(e: HttpError): boolean {
const signInAgain = page.getByRole('button', { name: /Se connecter avec localhost:3000/i });
let reconnected = false;
if (leftAfterReloadTo === 'picker' && (await signInAgain.count().catch(() => 0))) {
await signInAgain.first().click().catch(() => {});
await page.waitForTimeout(1500);
reconnected = await driveToApp(page);
// The same drive: from the app's root (the picker, no session left), sign-in click → pod login → consent → shell.
reconnected = (await driveLogin(page, 'recovered', USER, PASS, drive)).ok;
}
const consentReachedOnSignIn = hops.some(h => h.startsWith(' [recovered] [authorize'));
const consentReachedOnSignIn = redirects.some(r => r.startsWith('[recovered]') && r.includes(`${clean(POD_FRONT)}/authorize`));
const probeReadsCurrentAgain = reconnected && await waitFor(
() => statusSeen.some(s => s.phase === 'recovered' && s.status === 200 && isCurrent(s.body)),
15000,
@@ -421,7 +383,7 @@ function benign(e: HttpError): boolean {
revokedPhase4xx: errors4xx.filter(e => e.phase === 'revoked').map(e => `${e.status} ${e.method} ${clean(e.url)}`),
fatal4xx: fatal,
redirects,
hops,
hops: drive.hops,
consoleErrors,
ok: positive.ok && negative.ok && recovered.ok && fatal.length === 0,
};