ident
All repositories: gitoria
5.1 KB
// tests/signoutall.mjs — ticket ident#19 gate: "Sign out everywhere" makes every OPEN page on// every device switch to signed out at once (no click), and the sessions are deleted.// Own server :8702, two real headless Chromes (two cookie jars = two devices; debug ports// 8703-8709, --disable-gpu, closed at the end). Home page AND /apps page are open on device A.// node tests/signoutall.mjsimport { spawn } from 'node:child_process';import { rmSync, mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'node:fs';import { dirname, join, resolve } from 'node:path';import { fileURLToPath } from 'node:url';import { launchBrowser } from './cdp.mjs';if (!process.env.HL_CHROME && existsSync('/opt/google/chrome/chrome')) process.env.HL_CHROME = '/opt/google/chrome/chrome';const APP = resolve(dirname(fileURLToPath(import.meta.url)), '..');const G = join(APP, '.scratch/signoutall-gate');rmSync(G, { recursive: true, force: true });mkdirSync(join(G, 'sess'), { recursive: true });const PORT = 8702, BASE = `http://127.0.0.1:${PORT}`, SINK = join(G, 'mail.txt');const sleep = (ms) => new Promise((r) => setTimeout(r, ms));let passed = 0, failed = 0;const check = (n, ok, d = '') => { if (ok) { passed++; console.log('ok ' + n); } else { failed++; console.log('FAIL ' + n + (d ? ' — ' + d : '')); } };let log = '';const srv = spawn(join(APP, 'bin/hybriel'), ['project.hl'], { cwd: APP, env: { ...process.env, SMTP_HOST: '', IDENT_PORT: String(PORT), IDENT_STORAGE: join(G, 'store'), IDENT_SESSIONS: join(G, 'sess'), IDENT_MAIL_SINK: SINK, IDENT_IP_LIMIT: '1000', IDENT_IP_DAY_LIMIT: '1000' }, stdio: ['ignore', 'pipe', 'pipe'] });srv.stdout.on('data', (d) => log += d); srv.stderr.on('data', (d) => log += d);const browsers = [];async function finish() {for (const b of browsers) { try { await b.close(); } catch {} }try { process.kill(srv.pid); } catch {}writeFileSync(join(G, 'server.log'), log);console.log(`\n${passed} passed, ${failed} failed`);process.exit(failed ? 1 : 0);}try {for (let i = 0; i < 80; i++) { try { await fetch(BASE + '/'); break; } catch {} await sleep(250); }const email = '[email protected]';const code = async () => { for (let i = 0; i < 30; i++) { if (existsSync(SINK)) { const l = readFileSync(SINK, 'utf8').split('\n').filter((x) => x.startsWith(email + ' ')); if (l.length) return l.at(-1).split(' ')[1]; } await sleep(100); } };const connected = (p) => p.waitFor('!!window.__hl && window.__hl.socket && window.__hl.socket.readyState === 1');async function login(p) {await p.goto(BASE + '/');await p.waitForSelector('#email'); await connected(p);await p.type('#email', email, { clear: true });await p.click('#sendcode');await p.waitForSelector('#code');await connected(p); // ident#20: the code step is its own page (/code) — wait for it to hydrateawait sleep(300);await p.type('#code', await code(), { clear: true });await p.click('#verify');await p.waitForSelector('#signoutall');}const A = await launchBrowser({ debugPortRange: [8703, 8706] }); browsers.push(A);const B = await launchBrowser({ debugPortRange: [8707, 8709] }); browsers.push(B);const a1 = await A.newPage(), a2 = await A.newPage(), b1 = await B.newPage();await login(a1);await login(b1);await a2.goto(BASE + '/apps');await a2.waitFor('!!document.querySelector("#newapp, button")'); await connected(a2);await connected(a1);check('device A home + apps pages and device B are signed in', await a1.evaluate('!!document.querySelector("#signoutall")') && !(await a2.evaluate('!!document.querySelector("#email")')));check('two session files carry the account', readdirSync(join(G, 'sess')).filter((f) => JSON.parse(readFileSync(join(G, 'sess', f), 'utf8')).user).length === 2);b1.conn.onEvent((m) => { if (m.method === 'Page.javascriptDialogOpening' && m.sessionId === b1.sessionId) b1.send('Page.handleJavaScriptDialog', { accept: true }).catch(() => {}); });await b1.click('#signoutall');// NO click on device A: the pages must switch by themselvesawait a1.waitFor('!!document.querySelector("#email") && !document.querySelector("#signoutall")', { timeout: 8000, label: 'device A home signed out' }).then(() => check('device A home page switched to signed out without a click', true), (e) => check('device A home page switched to signed out without a click', false, e.message));await a2.waitFor('location.pathname === "/apps" && !!document.querySelector("#email, .lead")', { timeout: 8000 }).catch(() => {});const a2text = await a2.evaluate('document.body.innerText');check('device A apps page shows signed out too', !/Sign out everywhere/.test(a2text), a2text.slice(0, 200));await b1.waitFor('!!document.querySelector("#email")', { timeout: 8000 }).then(() => check('device B (the caller) is signed out', true), (e) => check('device B (the caller) is signed out', false, e.message));check('no session file carries the account any more', readdirSync(join(G, 'sess')).filter((f) => JSON.parse(readFileSync(join(G, 'sess', f), 'utf8')).user).length === 0);} catch (e) { check('gate ran without an exception', false, e.stack || e.message); }await finish();
Branches
- mainmain branch
Latest commits
- 74235815ident mission 009 (4/4): docs (README files map + same-output test, STATUS, LOG), report, tests/letcount.py + realdata-baseline/comparemre
- fe183516ident mission 009 (3/4): let only where reassigned — 293 never-reassigned lets are plain declarations; kept: reassigned, loop bodies, names of a file member, a name declared twice in one function; same outputmre
- d2e7f91bident mission 009 (2/4): one lib file per topic (login, accounts, identities, apps, invites, selector, notify + helpers, util), function routes as thin wrappers in lib/api.hl, project.hl = the map; same outputmre
- 91017164ident mission 009 (1/4): file moves — the root .hl files into lib/ (api.hl → lib/api-helpers.hl), styles.hl → components/styles.hl; imports adjusted, no other changemre
- f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmre
- ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
- a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
- 98226b41antcolony#40: mission references point to the moved missionsmre
- ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
- 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
- 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
- 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
- cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
- 81b15b7bState of 2026-09-27, before the move to gitoriamre