git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
65 lines
4.3 KiB
JavaScript
65 lines
4.3 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { consoleReads } from '../src/reads.mjs';
|
|
import { recordBootRefused, recordBootOk } from '../../cli/src/boot-status.mjs';
|
|
import { createOrg, templateRender, showOrg, archiveOrg } from '../../organization/src/org-service.mjs';
|
|
import { busStopForDelete } from '../../cli/src/org-bus.mjs';
|
|
import { listOrgs, resolveOrg } from '../../organization/src/org-registry.mjs';
|
|
import { organizationConsole } from '../src/organizations.mjs';
|
|
|
|
test('admission errors are visible while a healthy organization remains usable', async t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'org-admission-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const configDir = join(root, 'config'), dataRoot = join(root, 'data');
|
|
mkdirSync(configDir, { mode: 0o700 }); mkdirSync(dataRoot, { mode: 0o700 });
|
|
const principal = { provider: 'local-dev', iss: 'local-dev', sub: 'local-dev-owner' };
|
|
const ctx = { configDir, dataRoot, instance: 'readtest', principal };
|
|
for (const id of ['alpha', 'beta']) await createOrg(ctx, id, await templateRender(id));
|
|
const file = join(configDir, 'org-runtime.json'), registry = JSON.parse(readFileSync(file, 'utf8'));
|
|
registry.orgs.alpha.digest = '0'.repeat(64); writeFileSync(file, JSON.stringify(registry));
|
|
const console = organizationConsole({ ctx, listOrgs, showOrg, resolveOrg, orgPaths: () => ({}), busStatus: async () => ({ healthy: true }), options: ({ id }) => ({ id }) });
|
|
const rows = await console.list(principal);
|
|
assert.equal(rows.find(row => row.id === 'alpha')?.invalid, true);
|
|
assert.match(rows.find(row => row.id === 'alpha')?.reason, /document and registry disagree/);
|
|
assert.equal((await console.resolve('beta', principal)).id, 'beta');
|
|
});
|
|
|
|
test('an owner whose only organization is archived gets an empty list, not an error', async t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'org-archived-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const configDir = join(root, 'config'), dataRoot = join(root, 'data');
|
|
mkdirSync(configDir, { mode: 0o700 }); mkdirSync(dataRoot, { mode: 0o700 });
|
|
const principal = { provider: 'local-dev', iss: 'local-dev', sub: 'local-dev-owner' };
|
|
const ctx = { configDir, dataRoot, instance: 'readtest', principal };
|
|
await createOrg(ctx, 'alpha', await templateRender('alpha'));
|
|
const { revision } = await showOrg(ctx, 'alpha');
|
|
await archiveOrg(ctx, 'alpha', { ifRevision: revision, busStop: busStopForDelete });
|
|
const console = organizationConsole({ ctx, listOrgs, showOrg, resolveOrg, orgPaths: () => ({}), busStatus: async () => ({ healthy: true }), options: ({ id }) => ({ id }) });
|
|
assert.deepEqual(await console.list(principal), []);
|
|
});
|
|
|
|
test('offline boot status comes from the selected organization root', async t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'org-reads-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const alpha = join(root, 'alpha'), beta = join(root, 'beta');
|
|
recordBootOk(alpha, { business: 'alpha', file: 'alpha.json' });
|
|
recordBootRefused(beta, { business: 'beta', file: 'beta.json', reason: 'bad config' });
|
|
const reads = orgRoot => consoleReads({ root, system: { dataRoot: root }, orgRoot, business: 'alpha' });
|
|
assert.equal((await reads(alpha).busStatus()).boot.business, 'alpha');
|
|
assert.equal((await reads(beta).busStatus()).boot.business, 'beta');
|
|
});
|
|
|
|
test('settings reads the notifier choice created under the selected organization root', async t => {
|
|
const root = mkdtempSync(join(tmpdir(), 'org-reads-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const configDir = join(root, 'config'), dataRoot = join(root, 'data');
|
|
mkdirSync(configDir, { mode: 0o700 }); mkdirSync(dataRoot, { mode: 0o700 });
|
|
const ctx = { configDir, dataRoot, instance: 'readtest', principal: { provider: 'local-dev', iss: 'local-dev', sub: 'local-dev-owner' } };
|
|
const created = await createOrg(ctx, 'alpha', await templateRender('alpha'));
|
|
const reads = consoleReads({ root, configDir, system: { dataRoot }, orgRoot: created.root, business: 'alpha' });
|
|
assert.deepEqual((await reads.settings()).notifier, { binding: null });
|
|
});
|