Jason's 2026-11-12 ruling: the only deployed system is the dev environment on dragon-lin, so the system-type plug-in kind goes with no retired kind, fence, conversion or new refusal code. - plugins: system-type leaves KINDS; system-types.mjs and the five modules are deleted. An org file naming the kind refuses unknown-kind. - organization: the plugins.system-type check, systemTypesFence and the system-types.console right are removed. - bus: system-types.console, project.system-types[.set], the system-type half of the tester check, the runtime reads and system-type-not-enabled are removed. Step 3v-3w drops project_system_types, its index, its four guards and its rows; SCHEMA_VERSION is 3w (32 tables, 126 triggers, 5 views). - cli: mosaic org system-types and mosaic project system-types[-set] are removed; stack-release COUNTED and the budget supervisor's closed list move to 3w. - channel: the project.system-types verbs are removed. - webui: the System types page, route, dashboard row, chips and project selection are removed. - docs: REQ-SYS-1/2 now describe starter packs; README system-type text and the requirements titles follow.
249 lines
22 KiB
JavaScript
249 lines
22 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtemp, mkdir, rm } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { startServer } from '../src/serve.mjs';
|
|
import { createDevOwner } from '../src/dev-owner.mjs';
|
|
import { createOrgAdmin } from '../src/org-admin.mjs';
|
|
import { organizationConsole } from '../src/organizations.mjs';
|
|
import * as service from '../../organization/src/org-service.mjs';
|
|
import * as registry from '../../organization/src/org-registry.mjs';
|
|
import { orgPaths } from '../../cli/src/config.mjs';
|
|
import { busStopForDelete } from '../../cli/src/org-bus.mjs';
|
|
import { CliError } from '../../cli/src/errors.mjs';
|
|
import { actionTrail } from '../src/action-trail.mjs';
|
|
import { browser } from './browser.mjs';
|
|
import { close } from './fixture.mjs';
|
|
|
|
// E1: `bus` adds verbs to the admin's bus (busStart for a restore); `trail` gives the Console its action trail;
|
|
// `extra` adds to each organization's transports (the dashboard's bus reads and System types).
|
|
async function fixture(t, ids = ['alpha', 'beta'], { health = null, bus = {}, trail = false, extra = () => ({}) } = {}) {
|
|
const dir = await mkdtemp(join(tmpdir(), 'archive-browser-'));
|
|
const configDir = join(dir, 'config'), dataRoot = join(dir, 'data'); await mkdir(configDir, { mode: 0o700 }); await mkdir(dataRoot, { mode: 0o700 });
|
|
const ctx = { configDir, dataRoot, instance: 'test', principal: { provider: 'local-dev', iss: 'local-dev', sub: 'local-dev-owner' } };
|
|
for (const id of ids) await service.createOrg(ctx, id, await service.templateRender(id));
|
|
const busStatus = async () => ({ healthy: health ? health.up : false });
|
|
const organizations = organizationConsole({ ctx, listOrgs: registry.listOrgs, resolveOrg: registry.resolveOrg, showOrg: service.showOrg, orgPaths, busStatus, options: ({ id }) => ({ reads: { business: async () => ({ business: { id, projects: ['work'] } }), settings: async () => ({}) }, bus: {}, send: async () => ({}), ...extra(id) }) });
|
|
const admin = createOrgAdmin({ service, registry, bus: { busStatus, busStopForDelete, ...bus }, context: async () => ({ configDir, dataRoot, instance: 'test' }) });
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), orgAdmin: admin, organizations, send: async () => ({}), board: 'http://127.0.0.1:9', log: () => {}, ...(trail ? { trail: actionTrail(join(dir, 'console')) } : {}) });
|
|
const b = await browser({ machine: false }); t.after(async () => { await b.close(); await close(server); await rm(dir, { recursive: true, force: true }); });
|
|
const wait = async expression => { for (let i = 0; i < 400; i++) { try { if (await b.evaluate(expression)) return; } catch {} await new Promise(r => setTimeout(r, 30)); } throw Error(expression); };
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/settings/organizations`);
|
|
await wait(`document.querySelector('#org-open-id')?.value === 'alpha'`);
|
|
async function open(id) { await b.evaluate(`document.querySelector('#org-open-id').value=${JSON.stringify(id)};document.querySelector('#org-open-form button').click()`); await wait(`document.querySelector('#org-editor-title')?.textContent === ${JSON.stringify(`Organization: ${id}`)}`); }
|
|
async function shots(state, selector) {
|
|
if (!process.env.ORG_ARCHIVE_SHOTS) return;
|
|
await mkdir(process.env.ORG_ARCHIVE_SHOTS, { recursive: true });
|
|
for (const [width, height] of [[1400, 900], [390, 844]]) for (const mode of ['light', 'dark']) {
|
|
await b.viewport(width, height); await b.evaluate(`document.documentElement.dataset.mode=${JSON.stringify(mode)};document.querySelector(${JSON.stringify(selector)}).scrollIntoView({block:'start'})`);
|
|
await b.screenshot(join(process.env.ORG_ARCHIVE_SHOTS, `${state}-${width}-${mode}.png`));
|
|
}
|
|
}
|
|
return { b, wait, open, shots, ctx, port: server.address().port, dir };
|
|
}
|
|
|
|
test('archive and restore a non-current organization', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click()`);
|
|
assert.equal(await f.b.evaluate(`document.activeElement.id`), 'org-archive-confirm');
|
|
await f.shots('archive', '#org-archive');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-cancel').click()`);
|
|
assert.equal(await f.b.evaluate(`document.activeElement.id`), 'org-archive-open');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!document.querySelector('#org-archived').hidden`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'archived');
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-open-id').selectedOptions[0].textContent`), /archived/);
|
|
assert.deepEqual(await f.b.evaluate(`window.MosaicOrg.list.then(rows=>rows.map(row=>row.id))`), ['alpha']);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-save').disabled`), true);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-json-text').disabled`), true);
|
|
await f.shots('archived', '#org-archived');
|
|
const chat = await f.b.evaluate(`fetch('/api/orgs/beta/chat/projects').then(async r=>({status:r.status,body:await r.json()}))`);
|
|
assert.equal(chat.status, 409); assert.equal(chat.body.error, 'org-archived');
|
|
await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`!document.querySelector('#org-archive-success').hidden`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'active');
|
|
assert.deepEqual(await f.b.evaluate(`window.MosaicOrg.list.then(rows=>rows.map(row=>row.id))`), ['alpha', 'beta']);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-save').disabled`), false);
|
|
await f.shots('restored', '#org-archive-success');
|
|
});
|
|
|
|
test('archive does not announce success for an unexpected response', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`const original=fetch.bind(window);window.fetch=(input,init)=>String(input).endsWith('/archive')?Promise.resolve(new Response(JSON.stringify({outcome:'done',step:'bus'}),{status:200})):original(input,init);document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!!document.querySelector('#org-archive-error').textContent`);
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-archive-error').textContent`), /outcome is unknown/i);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-archive-final').textContent`), 'Archive');
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-archived').hidden`), true);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'active');
|
|
});
|
|
|
|
test('archiving the current organization switches to the remaining one', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('alpha');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`window.MosaicOrg?.id === 'beta' && document.querySelector('header')?.textContent.includes('beta')`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'alpha').state, 'archived');
|
|
});
|
|
|
|
test('opening another editor resets the archive confirmation and error', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-error').textContent='old error';document.querySelector('#org-archive-final').disabled=true`);
|
|
await f.open('alpha');
|
|
assert.deepEqual(await f.b.evaluate(`({trigger:document.querySelector('#org-archive-open').hidden,panel:document.querySelector('#org-archive-confirm').hidden,error:document.querySelector('#org-archive-error').textContent,disabled:document.querySelector('#org-archive-final').disabled})`), {trigger:false,panel:true,error:'',disabled:false});
|
|
});
|
|
|
|
// E1 (P1-4): one Restore restores the organization and starts its bus; the receipt names both.
|
|
const startingBus = (health, fails = 0) => {
|
|
const starts = [];
|
|
return { starts, busStart: async (ctx, id, opts) => { starts.push([id, opts]); if (starts.length <= fails) throw new CliError('bus failed to start; see the bus log', 3); health.up = true; return { org: id, pid: 4242, socket: join(ctx.dataRoot, 'bus.sock'), root: join(ctx.dataRoot, 'org-runtime', id) }; } };
|
|
};
|
|
const archiveBeta = async f => { await f.open('beta'); await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`); await f.wait(`!document.querySelector('#org-archived').hidden`); };
|
|
|
|
test('E1 (P1-4): Restore restores the organization and starts its bus, with one receipt naming both', { timeout: 45000 }, async t => {
|
|
const health = { up: false }, bus = startingBus(health);
|
|
const f = await fixture(t, ['alpha', 'beta'], { health, bus, trail: true }); await archiveBeta(f);
|
|
await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`!document.querySelector('#org-archive-success').hidden`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'active');
|
|
assert.deepEqual(bus.starts, [['beta', { detach: true, devLocalOwner: true, pm: false }]]);
|
|
const text = await f.b.evaluate(`document.querySelector('#org-archive-success').innerText`);
|
|
assert.match(text, /^Organization restored\. Bus started\. It answers\./);
|
|
assert.match(text, /Same as mosaic bus start beta --dev-local-owner --detach/);
|
|
assert.doesNotMatch(text, /remains stopped/);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('[data-restore-bus]')`), null, 'a running bus needs no Restart bus');
|
|
assert.deepEqual(await f.b.evaluate(`window.MosaicOrg.list.then(rows=>rows.map(row=>[row.id,row.busHealthy]))`), [['alpha', true], ['beta', true]]);
|
|
await f.shots('e1-restored-started', '#org-archive-success');
|
|
});
|
|
|
|
test('E1 (P1-4): a bus that does not start after a good restore is said so, and Restart bus starts it', { timeout: 45000 }, async t => {
|
|
const health = { up: false }, bus = startingBus(health, 1);
|
|
const f = await fixture(t, ['alpha', 'beta'], { health, bus, trail: true }); await archiveBeta(f);
|
|
await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`!document.querySelector('#org-archive-success').hidden`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'active', 'the restore stands');
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-archive-success').innerText`), /^Organization restored\. The bus did not start: bus failed to start; see the bus log/);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('[data-restore-bus]').textContent`), 'Restart bus');
|
|
await f.shots('e1-restored-bus-failed', '#org-archive-success');
|
|
await f.b.evaluate(`document.querySelector('[data-restore-bus]').click()`);
|
|
await f.wait(`/It answers/.test(document.querySelector('[data-restore-bus-receipt]')?.textContent || '')`);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('[data-restore-bus-receipt]').textContent`), 'Bus started. It answers.');
|
|
assert.equal(await f.b.evaluate(`document.querySelector('[data-restore-bus]').disabled`), true);
|
|
assert.deepEqual(bus.starts.map(([id]) => id), ['beta', 'beta']);
|
|
await f.shots('e1-restored-bus-restarted', '#org-archive-success');
|
|
});
|
|
|
|
test('E1 (P1-4): restoring the only organization reloads into it and still shows the restore and bus receipt', { timeout: 45000 }, async t => {
|
|
const health = { up: false }, bus = startingBus(health);
|
|
const f = await fixture(t, ['alpha'], { health, bus, trail: true }); await f.open('alpha');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!window.MosaicOrg && document.querySelector('#org-open-id')?.value === 'alpha'`);
|
|
await f.open('alpha'); await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`window.MosaicOrg?.id === 'alpha' && document.querySelector('#org-archive-success')?.hidden === false`);
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-archive-success').innerText`), /^Organization restored\. Bus started\. It answers\./);
|
|
assert.deepEqual(bus.starts.map(([id]) => id), ['alpha']);
|
|
// The kept receipt is shown once: a later visit does not repeat it.
|
|
assert.equal(await f.b.evaluate(`Object.keys(sessionStorage).filter(k=>k.startsWith('mosaic-org-restored:')).length`), 0);
|
|
});
|
|
|
|
test('E1 (P1-5): after restoring another organization, its dashboard shows the bus running and its organization file', { timeout: 45000 }, async t => {
|
|
const health = { up: false }, bus = startingBus(health);
|
|
const extra = id => ({
|
|
reads: { business: async () => ({ business: { id, human: 'local-dev-owner', arbiters: [], projects: [], vars: {}, instances: [] } }), settings: async () => ({}), queue: async () => ({ rows: [] }), busStatus: async () => ({}) },
|
|
bus: { instances: async () => ({ business: id, revision: 'a'.repeat(64), launch: true, noHire: [], retired: [], definitions: {}, instances: {} }), messages: async () => ({ messages: [], unread: 0 }), agents: async () => [], 'thread.claims': async () => [], inbox: async () => [], tasks: async () => [] },
|
|
});
|
|
const f = await fixture(t, ['alpha', 'beta'], { health, bus, trail: true, extra }); await archiveBeta(f);
|
|
await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`/Bus started\. It answers\./.test(document.querySelector('#org-archive-success')?.innerText || '')`);
|
|
await f.wait(`!!document.querySelector('#workspace-nav [data-workspace-org=beta]')`);
|
|
await f.b.evaluate(`location.hash = '#/orgs/beta/organization'`);
|
|
await f.wait(`/bus running, read/.test(document.querySelector('[data-org-dashboard]')?.innerText || '')`);
|
|
assert.match(await f.b.evaluate(`document.querySelector('[data-org-dashboard] [data-org-setting="Identity"] .v').textContent`), / · beta · /);
|
|
assert.deepEqual(bus.starts.map(([id]) => id), ['beta']);
|
|
});
|
|
|
|
test('restore does not announce success for an unexpected response', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!document.querySelector('#org-archived').hidden`);
|
|
await f.b.evaluate(`const original=fetch.bind(window);window.fetch=(input,init)=>String(input).endsWith('/restore')?Promise.resolve(new Response(JSON.stringify({outcome:'done',step:'archive'}),{status:200})):original(input,init);document.querySelector('#org-restore').click()`);
|
|
await f.wait(`!!document.querySelector('#org-archive-error').textContent`);
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-archive-error').textContent`), /outcome is unknown/i);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-archive-success').hidden`), true);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'archived');
|
|
});
|
|
|
|
|
|
test('archiving the last active organization shows the no-organization state', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t, ['alpha']); await f.open('alpha');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!window.MosaicOrg && document.querySelector('#org-open-id')?.value === 'alpha'`);
|
|
assert.equal((await registry.listOrgs(f.ctx))[0].state, 'archived');
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-open-id').selectedOptions[0].textContent`), /archived/);
|
|
await f.open('alpha'); await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`window.MosaicOrg?.id === 'alpha'`);
|
|
assert.equal((await registry.listOrgs(f.ctx))[0].state, 'active');
|
|
});
|
|
|
|
test('fresh archived organization page offers owner restore while writes stay refused', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!document.querySelector('#org-archived').hidden`);
|
|
await f.b.navigate(`http://127.0.0.1:${f.port}/#/orgs/beta/settings/organizations?org=beta`);
|
|
await f.wait(`document.querySelector('#org-archived') && !document.querySelector('#org-archived').hidden`);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-restore').disabled`), false);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-delete-open').disabled`), false);
|
|
assert.match(await f.b.evaluate(`document.querySelector('#org-archived').innerText`), /mosaic org restore beta --if-revision \d+/);
|
|
const refused = await f.b.evaluate(`Promise.all(['/api/orgs/beta/chat/projects','/api/orgs/beta/chat/threads/00000000-0000-0000-0000-000000000000/messages'].map(url=>fetch(url,{method:'POST',headers:{'content-type':'application/json'},body:'{}'}).then(r=>r.status)))`);
|
|
assert.deepEqual(refused, [409, 409]);
|
|
await f.b.evaluate(`document.querySelector('#org-restore').click()`);
|
|
await f.wait(`!document.querySelector('#org-archive-success').hidden`);
|
|
assert.equal((await registry.listOrgs(f.ctx)).find(e => e.id === 'beta').state, 'active');
|
|
});
|
|
|
|
test('owner can start a down bus after setup recorded done and sees recovery guidance on refusal', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t);
|
|
await f.b.evaluate(`(()=>{const old=window.fetch;window.fetch=async (input,init={})=>{const u=String(input);if(u.endsWith('/setup') && (!init.method || init.method==='GET'))return new Response(JSON.stringify({notifications:{state:'done'},queue:{state:'done'},projects:[],bus:{state:'done',verb:'mosaic bus start alpha'}}),{status:200});if(u.endsWith('/setup/bus') && init.method==='POST')return new Response(JSON.stringify({error:'stale-lock',message:'stale bus host lock'}),{status:409});return old(input,init)}})()`);
|
|
await f.open('alpha');
|
|
// The setup steps render after the editor title, once the setup and organization list reads answer.
|
|
await f.wait(`!!document.querySelector('[data-step="bus"] [data-run="bus"]:not(:disabled)')`);
|
|
await f.b.evaluate(`document.querySelector('[data-step="bus"] [data-run="bus"]').click()`);
|
|
await f.wait(`document.querySelector('[data-step="bus"]').innerText.includes('stale bus host lock')`);
|
|
assert.match(await f.b.evaluate(`document.querySelector('[data-step="bus"]').innerText`), /mosaic bus recover alpha/);
|
|
});
|
|
|
|
// E2E5 review P2: the list read by Refresh, not only opening the organization, decides whether Start shows.
|
|
test('an open Settings page offers Start when a refreshed org list reads the bus down after setup recorded done', { timeout: 45000 }, async t => {
|
|
const health = { up: true };
|
|
const f = await fixture(t, ['alpha', 'beta'], { health });
|
|
await f.b.evaluate(`(()=>{const old=window.fetch;window.fetch=async (input,init={})=>{const u=String(input);if(u.endsWith('/setup') && (!init.method || init.method==='GET'))return new Response(JSON.stringify({notifications:{state:'done'},queue:{state:'done'},projects:[],bus:{state:'done',verb:'mosaic bus start alpha'}}),{status:200});return old(input,init);};})()`);
|
|
await f.open('alpha');
|
|
await f.wait(`document.querySelector('[data-step="bus"]')?.textContent.includes('Done')`);
|
|
assert.equal(await f.b.evaluate(`!!document.querySelector('[data-step="bus"] [data-run="bus"]')`), false);
|
|
health.up = false;
|
|
await f.b.evaluate(`document.querySelector('#refresh').click()`);
|
|
await f.wait(`!!document.querySelector('[data-step="bus"] [data-run="bus"]:not(:disabled)')`);
|
|
health.up = true;
|
|
await f.b.evaluate(`document.querySelector('#refresh').click()`);
|
|
await f.wait(`!document.querySelector('[data-step="bus"] [data-run="bus"]')`);
|
|
});
|
|
|
|
for (const capability of [{ user:true, orgSetup:false }, { tester:true, orgSetup:false }, { orgSetup:false }]) {
|
|
test(`archived restore stays disabled without owner capability ${JSON.stringify(capability)}`, { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()`);
|
|
await f.wait(`!document.querySelector('#org-archived').hidden`);
|
|
await f.b.call('Page.addScriptToEvaluateOnNewDocument', {source:`{const old=window.fetch;window.fetch=(input,init)=>input instanceof URL && input.pathname==='/api/config'?Promise.resolve(new Response(JSON.stringify(${JSON.stringify(capability)}),{status:200})):old(input,init)}`});
|
|
await f.b.navigate(`http://127.0.0.1:${f.port}/#/orgs/beta/settings/organizations?org=beta`);
|
|
await f.wait(`document.querySelector('#org-archived') && !document.querySelector('#org-archived').hidden`);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-restore').disabled`),true);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-delete-open').disabled`),true);
|
|
});
|
|
}
|
|
|
|
test('archive displays progress while its request is in flight', { timeout: 45000 }, async t => {
|
|
const f = await fixture(t); await f.open('beta');
|
|
await f.b.evaluate(`{const old=window.fetch;window.fetch=(input,init)=>String(input).endsWith('/archive')?new Promise(resolve=>window.releaseArchive=()=>old(input,init).then(resolve)):old(input,init);document.querySelector('#org-archive-open').click();document.querySelector('#org-archive-final').click()}`);
|
|
assert.equal(await f.b.evaluate(`document.querySelector('#org-archive-final').textContent`),'Archiving…');
|
|
await f.b.evaluate(`window.releaseArchive()`); await f.wait(`!document.querySelector('#org-archived').hidden`);
|
|
});
|