mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
b55d443b44
* fix(web): retry transient home balance preflight * refactor(web): streamline home submit preflight * fix(web): keep local submit independent from cloud identity * fix(web): preserve local and legacy submit lanes * fix(web): keep in-memory workspace witness valid * fix(web): retain workspace preflight compatibility * fix(web): require workspace witness for team plugin apply * fix(plugins): apply exact local plugin source * fix(web): invalidate legacy witness on identity change * refactor: keep Home submit local-first * test: align local-first creation coverage * fix: preserve exact local plugin context * fix: fence exact local plugin use * fix: disable inexact legacy plugin fallback * test: mock exact plugin apply in visuals * fix: reject unsupported exact project source * Revert "fix: reject unsupported exact project source" This reverts commit99e0cf23c1. * Revert "test: mock exact plugin apply in visuals" This reverts commit33b36688c7. * Revert "fix: disable inexact legacy plugin fallback" This reverts commitd1b4d9bcaa. * Revert "fix: fence exact local plugin use" This reverts commitaebaf6a20b. * Revert "fix: preserve exact local plugin context" This reverts commitaa6ab67631. * Revert "fix(plugins): apply exact local plugin source" This reverts commite776a7d079. * fix: reconcile local submit selections * fix: preserve cold cloud submit handoff * test: seed active team design-system binding * fix(plugins): preserve exact local source semantics * test(web): align Home failure copy after main merge * fix(home): preserve staged resource catalog scope
175 lines
5.7 KiB
TypeScript
175 lines
5.7 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import {
|
|
ensureWorkspaceResource,
|
|
openDatabase,
|
|
updateWorkspaceResource,
|
|
} from '../src/db.js';
|
|
import {
|
|
materializeWorkspaceScopedTeamResource,
|
|
teamResourceMaterializationDir,
|
|
} from '../src/collab/team-resource-materialization.js';
|
|
import {
|
|
localPluginRegistryScope,
|
|
resolveLocalPluginBySource,
|
|
resolvePluginFolder,
|
|
resolvePluginSnapshot,
|
|
upsertInstalledPlugin,
|
|
workspaceTeamPluginBindingResourceId,
|
|
} from '../src/plugins/index.js';
|
|
|
|
const roots: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(roots.splice(0).map((root) =>
|
|
rm(root, { recursive: true, force: true }),
|
|
));
|
|
});
|
|
|
|
async function pluginManifest(folder: string, title: string): Promise<void> {
|
|
await mkdir(folder, { recursive: true });
|
|
await writeFile(
|
|
path.join(folder, 'open-design.json'),
|
|
JSON.stringify({ name: 'shared-id', title, version: '1.0.0' }),
|
|
);
|
|
}
|
|
|
|
describe('resolveLocalPluginBySource', () => {
|
|
it('derives local registry provenance only for an exact Team source', () => {
|
|
expect(localPluginRegistryScope({
|
|
id: 'shared-id',
|
|
source: 'team:plugin:workspace-a:shared-id',
|
|
})).toEqual({ workspaceId: 'workspace-a', workspaceMemberId: null });
|
|
expect(localPluginRegistryScope({
|
|
id: 'shared-id',
|
|
source: 'local:personal:shared-id',
|
|
})).toBeUndefined();
|
|
});
|
|
|
|
it('selects the exact local record when Personal and Team share an id', async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), 'od-local-plugin-source-'));
|
|
roots.push(root);
|
|
const dataDir = path.join(root, 'data');
|
|
const pluginsRoot = path.join(dataDir, 'plugins');
|
|
const db = openDatabase(root, { dataDir });
|
|
|
|
const personalFolder = path.join(root, 'personal-plugin');
|
|
await pluginManifest(personalFolder, 'Personal copy');
|
|
const personal = await resolvePluginFolder({
|
|
folder: personalFolder,
|
|
folderId: 'shared-id',
|
|
sourceKind: 'local',
|
|
source: 'local:personal:shared-id',
|
|
});
|
|
if (!personal.ok) throw new Error(personal.errors.join('; '));
|
|
upsertInstalledPlugin(db, personal.record);
|
|
|
|
const team = await materializeWorkspaceScopedTeamResource({
|
|
kindRoot: pluginsRoot,
|
|
identity: {
|
|
kind: 'plugin',
|
|
workspaceId: 'workspace-a',
|
|
resourceId: 'shared-id',
|
|
hubResourceId: 'hub-team-shared-id',
|
|
},
|
|
storageName: 'shared-id',
|
|
pullInto: (dir) => pluginManifest(dir, 'Team copy'),
|
|
verifyWorkspaceScope: async () => true,
|
|
verifyStillShared: async () => true,
|
|
});
|
|
if (team.status !== 'committed') throw new Error('fixture did not materialize');
|
|
|
|
const exactTeamPlugin = await resolveLocalPluginBySource({
|
|
db,
|
|
id: 'shared-id',
|
|
source: team.sourceKey,
|
|
userPluginsRoot: pluginsRoot,
|
|
});
|
|
expect(exactTeamPlugin).toMatchObject({
|
|
id: 'shared-id',
|
|
title: 'Team copy',
|
|
source: 'team:plugin:workspace-a:shared-id',
|
|
fsPath: teamResourceMaterializationDir(
|
|
pluginsRoot,
|
|
'workspace-a',
|
|
'shared-id',
|
|
'shared-id',
|
|
),
|
|
});
|
|
await expect(resolveLocalPluginBySource({
|
|
db,
|
|
id: 'shared-id',
|
|
source: 'local:personal:shared-id',
|
|
userPluginsRoot: pluginsRoot,
|
|
})).resolves.toMatchObject({ title: 'Personal copy' });
|
|
|
|
db.prepare(`
|
|
INSERT INTO projects (id, name, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?)
|
|
`).run('team-project', 'Team project', 1, 1);
|
|
const snapshot = resolvePluginSnapshot({
|
|
db,
|
|
body: { pluginId: 'shared-id' },
|
|
projectId: 'team-project',
|
|
registry: { skills: [], designSystems: [], craft: [], atoms: [] },
|
|
plugin: exactTeamPlugin ?? undefined,
|
|
});
|
|
expect(snapshot?.ok).toBe(true);
|
|
if (snapshot?.ok) expect(snapshot.snapshot.pluginTitle).toBe('Team copy');
|
|
});
|
|
|
|
it('rejects a forged Team source without a local materialization marker', async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), 'od-local-plugin-source-'));
|
|
roots.push(root);
|
|
const dataDir = path.join(root, 'data');
|
|
const db = openDatabase(root, { dataDir });
|
|
|
|
await expect(resolveLocalPluginBySource({
|
|
db,
|
|
id: 'shared-id',
|
|
source: 'team:plugin:workspace-forged:shared-id',
|
|
userPluginsRoot: path.join(dataDir, 'plugins'),
|
|
})).resolves.toBeNull();
|
|
});
|
|
|
|
it('rejects a Team source after its local binding is tombstoned', async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), 'od-local-plugin-source-'));
|
|
roots.push(root);
|
|
const dataDir = path.join(root, 'data');
|
|
const pluginsRoot = path.join(dataDir, 'plugins');
|
|
const db = openDatabase(root, { dataDir });
|
|
const source = 'team:plugin:workspace-a:shared-id';
|
|
|
|
await materializeWorkspaceScopedTeamResource({
|
|
kindRoot: pluginsRoot,
|
|
identity: {
|
|
kind: 'plugin',
|
|
workspaceId: 'workspace-a',
|
|
resourceId: 'shared-id',
|
|
hubResourceId: 'hub-team-shared-id',
|
|
},
|
|
storageName: 'shared-id',
|
|
pullInto: (dir) => pluginManifest(dir, 'Retired Team copy'),
|
|
verifyWorkspaceScope: async () => true,
|
|
verifyStillShared: async () => true,
|
|
});
|
|
const bindingId = workspaceTeamPluginBindingResourceId('workspace-a', 'shared-id');
|
|
ensureWorkspaceResource(db, 'plugin', 'workspace-a', bindingId, {
|
|
visibility: 'team',
|
|
resourceState: 'active',
|
|
});
|
|
updateWorkspaceResource(db, 'plugin', 'workspace-a', bindingId, {
|
|
resourceState: 'deleted',
|
|
});
|
|
|
|
await expect(resolveLocalPluginBySource({
|
|
db,
|
|
id: 'shared-id',
|
|
source,
|
|
userPluginsRoot: pluginsRoot,
|
|
})).resolves.toBeNull();
|
|
});
|
|
});
|