Files
stack/packages/webui/tests/org-list-refresh-browser.test.mjs
jason.woltje e24e7e93a2 feat(webui): one shell, a system dashboard, organization pages as drill-ins (#1873, lane sp1)
#/dashboard is the system landing route: one row per organization with its bus
state, agent count (organization files), project count and needs-you count,
each only when a read gave it, and active agents. Opening an organization from
the sidebar or a dashboard row is a route inside the same page: no reload, a
Dashboard / <organization> breadcrumb, and later /api reads scoped to it through
the existing fetch wrapper, whose organization is now a variable the router sets
(mosaic-org-switch event). #/orgs/<id>/... stays the address. An organization
whose bus is down says so on the dashboard and on its page, with the reason the
workspace read gives.
2026-11-14 22:35:00 -06:00

58 lines
4.2 KiB
JavaScript

// On a selected org's page, the header shows why the org list failed or which org is broken,
// and Retry re-reads the list and clears the banner once it recovers (polish review F3, P19 #868).
// #1873 SP1: the breadcrumb names the open organization; the header no longer does.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as fs from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createOrg, showOrg, templateRender } from '../../organization/src/org-service.mjs';
import { listOrgs, resolveOrg } from '../../organization/src/org-registry.mjs';
import { organizationConsole } from '../src/organizations.mjs';
import { startServer } from '../src/serve.mjs';
import { createDevOwner } from '../src/dev-owner.mjs';
import { browser } from './browser.mjs';
const principal = { provider: 'local-dev', iss: 'local-dev', sub: 'local-dev-owner' };
async function setup(t) {
const root = await fs.mkdtemp(join(tmpdir(), 'orgref-'));
const ctx = { configDir: join(root, 'config'), dataRoot: join(root, 'data'), instance: 'test', principal };
await fs.mkdir(ctx.configDir, { mode: 0o700 }); await fs.mkdir(ctx.dataRoot, { mode: 0o700 });
for (const id of ['alpha', 'beta']) await createOrg(ctx, id, await templateRender(id));
let listRequests = 0;
const organizations = organizationConsole({ ctx, listOrgs: async (...args) => { listRequests++; return listOrgs(...args); }, showOrg, resolveOrg, orgPaths: () => ({}), busStatus: async () => ({ healthy: true }), options: ({ id }) => ({ reads: { business: async () => ({ business: { id, projects: [] } }) }, bus: {} }) });
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, log: () => {} });
const b = await browser({ machine: false });
t.after(async () => { await b.close(); await new Promise(r => { server.close(r); server.closeIdleConnections(); }); await fs.rm(root, { recursive: true, force: true }); });
return { ctx, b, base: `http://127.0.0.1:${server.address().port}`, requests: () => listRequests };
}
const wait = (b, expr) => b.evaluate(`(async()=>{for(let i=0;i<200;i++){if(${expr})return;await new Promise(r=>setTimeout(r,25))}throw Error(${JSON.stringify(expr)})})()`);
const bannerText = "document.getElementById('org-list-banner')?.textContent || ''";
test('a registry error shows on a selected org page and Retry recovers', async t => {
const f = await setup(t), file = join(f.ctx.configDir, 'org-runtime.json');
const original = await fs.readFile(file, 'utf8'); await fs.writeFile(file, '{');
await f.b.navigate(`${f.base}/#/orgs/alpha/board`);
await wait(f.b, `(${bannerText}).includes('Organization registry could not be read') && document.querySelector('#held [data-retry]')`);
const before = f.requests(); await fs.writeFile(file, original);
await f.b.evaluate("document.querySelector('#held [data-retry]').click()");
await wait(f.b, "!!document.querySelector('#breadcrumbs [data-crumb-org]') && !document.getElementById('org-list-banner')");
assert.ok(f.requests() > before, 'Retry re-read the organization list');
assert.equal(await f.b.evaluate("document.querySelectorAll('#breadcrumbs [data-crumb-org]').length"), 1);
});
test('a broken org banner is replaced, not stacked, and clears after repair', async t => {
const f = await setup(t), file = join(f.ctx.configDir, 'org-runtime.json');
const original = await fs.readFile(file, 'utf8'), reg = JSON.parse(original);
reg.orgs.beta.digest = '0'.repeat(64); await fs.writeFile(file, JSON.stringify(reg));
await f.b.navigate(`${f.base}/#/orgs/alpha/board`);
await wait(f.b, `(${bannerText}).includes('beta: invalid') && document.querySelector('#held [data-retry]')`);
await f.b.evaluate('window.MosaicRefreshOrganizations()');
await wait(f.b, `(${bannerText}).includes('beta: invalid')`);
assert.equal(await f.b.evaluate("document.querySelectorAll('#org-list-banner').length"), 1);
await fs.writeFile(file, original);
await f.b.evaluate("document.querySelector('#held [data-retry]').click()");
await wait(f.b, "!document.getElementById('org-list-banner')");
assert.equal(await f.b.evaluate("document.querySelectorAll('#breadcrumbs [data-crumb-org]').length"), 1);
});