coulomb-social/scripts/test-communication-browser.mjs
tegwick 268a9421ff
Some checks failed
Use-case regression gate / usecase-regressions (push) Has been cancelled
Bind loopback communication journeys for WP-0015-T04
Exercise reset, registration and invitation against an isolated issuer
and mailbox. The nodes stay unestablished and do not contact production.

Assistant: grok
Assistant-Session: 01a12cc2-e3b2-77a3-8aef-9cc60e577fee
2026-10-11 23:46:54 +02:00

145 lines
7.1 KiB
JavaScript

import assert from 'node:assert/strict';
import { chromium } from 'playwright';
const [appUrl, issuerUrl, password] = process.argv.slice(2);
const app = new URL(appUrl);
const issuer = new URL(issuerUrl);
for (const base of [app, issuer]) {
if (base.protocol !== 'http:' || !['localhost', '127.0.0.1'].includes(base.hostname)) {
throw new Error('Communication checks require isolated loopback servers');
}
}
const allowed = new Set([app.origin, issuer.origin]);
const browser = await chromium.launch({headless: true});
try {
const context = await browser.newContext();
const aborted = [];
await context.route('**/*', route => {
const origin = new URL(route.request().url()).origin;
if (allowed.has(origin)) return route.continue();
aborted.push(route.request().url());
return route.abort();
});
const page = await context.newPage();
page.setDefaultTimeout(10000);
let callbackUrl = '';
page.on('request', request => {
const url = request.url();
if (url.includes('/auth/callback/') && !callbackUrl) callbackUrl = url;
});
await page.goto(`${issuer.origin}/recover`);
await page.getByRole('button', {name: 'Send reset'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
const mailboxText = await page.locator('main').innerText();
assert.match(mailboxText, /Local mailbox/);
assert.doesNotMatch(mailboxText, /handle=/);
assert.equal(mailboxText.includes(password), false);
await page.getByRole('button', {name: 'Use reset', exact: true}).click();
await page.getByLabel('Password').fill(password);
await page.getByRole('button', {name: 'Save password'}).click();
assert.match(await page.locator('main').innerText(), /Password updated/);
assert.equal((await page.locator('main').innerText()).includes(password), false);
await page.goto(app.origin + '/');
await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click();
await page.waitForURL(url => url.origin === issuer.origin && url.pathname === '/authorize');
await page.getByRole('button', {name: 'Continue'}).click();
await page.waitForURL('**/app/');
assert.ok(callbackUrl);
await page.goto(app.origin + '/account/session/');
const sessionText = await page.locator('main').innerText();
const sessionHtml = await page.content();
const resetCode = new URL(callbackUrl).searchParams.get('code');
assert.match(sessionText, /member-recovered/);
assert.match(sessionText, /member@example\.test/);
assert.equal(sessionText.includes(password), false);
assert.equal(sessionHtml.includes(password), false);
assert.equal(sessionHtml.includes(resetCode), false);
const replay = await page.goto(callbackUrl);
assert.equal(replay.status(), 400);
assert.match(await page.locator('body').innerText(), /Invalid OIDC callback state/);
await page.goto(`${issuer.origin}/recover?expired=1`);
await page.getByRole('button', {name: 'Send reset'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
await page.getByRole('button', {name: 'Use expired reset'}).click();
assert.match(await page.locator('main').innerText(), /Handle expired/);
await page.goto(app.origin + '/auth/logout/');
await page.waitForURL(app.origin + '/');
await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click();
await page.getByLabel('Subject').fill('member-other');
await page.getByLabel('Name').fill('Other Member');
await page.getByLabel('Email').fill('member@example.test');
await page.getByRole('button', {name: 'Continue'}).click();
await page.waitForURL('**/app/');
await page.goto(app.origin + '/account/session/');
const other = await page.locator('main').innerText();
assert.match(other, /member-other/);
assert.doesNotMatch(other, /member-recovered/);
await page.goto(app.origin + '/auth/logout/');
await page.waitForURL(app.origin + '/');
await page.goto(app.origin + '/auth/register/');
await page.waitForURL(`${issuer.origin}/register`);
assert.match(await page.locator('main').innerText(), /Registration sink/);
await page.getByRole('button', {name: 'Send verification'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
await page.getByRole('button', {name: 'Cancel verification'}).click();
assert.match(await page.locator('main').innerText(), /Handle cancelled/);
await page.goto(`${issuer.origin}/mailbox`);
assert.match(await page.locator('main').innerText(), /verification cancelled/);
assert.equal(await page.getByRole('button', {name: 'Use verification'}).count(), 0);
await page.goto(`${issuer.origin}/register`);
await page.getByRole('button', {name: 'Send verification'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
await page.getByRole('button', {name: 'Use verification'}).click();
assert.match(await page.locator('main').innerText(), /Verification consumed/);
assert.match(await page.locator('main').innerText(), /does not sign in/);
await page.goto(app.origin + '/');
assert.equal(
await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).count(),
1,
);
const refused = new URL('/authorize', issuer.origin);
refused.searchParams.set('client_id', 'intruder');
refused.searchParams.set('redirect_uri', 'http://127.0.0.1:9/callback');
refused.searchParams.set('response_type', 'code');
refused.searchParams.set('state', 'x');
refused.searchParams.set('code_challenge', 'not-used');
refused.searchParams.set('code_challenge_method', 'S256');
await page.goto(refused.toString());
assert.match(await page.locator('main').innerText(), /Client refused/);
await page.goto(app.origin + '/');
await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click();
await page.getByLabel('Tenant').fill('tenant:other');
await page.getByRole('button', {name: 'Continue'}).click();
assert.match(await page.locator('main').innerText(), /Tenant refused/);
await page.goto(`${issuer.origin}/invite`);
await page.getByLabel('Caller').fill('intruder');
await page.getByRole('button', {name: 'Send invitation'}).click();
assert.match(await page.locator('main').innerText(), /Caller refused/);
await page.goto(`${issuer.origin}/invite`);
await page.getByLabel('Caller').fill('coulomb-social-test');
await page.getByLabel('Suppress').check();
await page.getByRole('button', {name: 'Send invitation'}).click();
assert.match(await page.locator('main').innerText(), /Invitation suppressed/);
await page.goto(`${issuer.origin}/invite`);
await page.getByLabel('Caller').fill('coulomb-social-test');
await page.getByRole('button', {name: 'Send invitation'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
await page.goto(`${issuer.origin}/invite`);
await page.getByLabel('Caller').fill('coulomb-social-test');
await page.getByRole('button', {name: 'Send invitation'}).click();
await page.getByRole('link', {name: 'Open mailbox'}).click();
assert.equal(await page.getByRole('button', {name: 'Open invitation'}).count(), 2);
assert.equal(aborted.length, 0);
console.log('Isolated recovery, registration and invitation journey passed');
} finally {
await browser.close();
}