In organization mode #/ is the cross-organization dashboard (sample 02), #/projects lists every organization's projects with sort, filters and the shared By org / Flat grouping (sample 04), and #/inbox lists decisions across organizations with per-organization decide paths and terminal commands carrying --org and the full id (sample 05). The per-organization board moves to #/board and the organization's own project list to #/chat, which keeps the project surface. Reads go through workspace-data.js; a read an organization could not make says not read, never zero. Polling repaints in place and keeps focus, caret and scroll.
57 lines
4.1 KiB
JavaScript
57 lines
4.1 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).
|
|
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('#current-organization') && !document.getElementById('org-list-banner')");
|
|
assert.ok(f.requests() > before, 'Retry re-read the organization list');
|
|
assert.equal(await f.b.evaluate("document.querySelectorAll('#current-organization').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('#current-organization').length"), 1);
|
|
});
|