#/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.
58 lines
4.2 KiB
JavaScript
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);
|
|
});
|