Files
Toju/toju-app/src/app/domains/plugins/domain/logic/plugin-dependency-resolver.logic.spec.ts
2026-04-29 01:14:14 +02:00

83 lines
2.8 KiB
TypeScript

import type { TojuPluginManifest } from '../../../../shared-kernel';
import { resolvePluginLoadOrder } from './plugin-dependency-resolver.logic';
function manifest(id: string, overrides: Partial<TojuPluginManifest> = {}): TojuPluginManifest {
return {
apiVersion: '1.0.0',
compatibility: {
minimumTojuVersion: '1.0.0'
},
description: `${id} plugin`,
entrypoint: './main.js',
id,
kind: 'client',
schemaVersion: 1,
title: id,
version: '1.0.0',
...overrides
};
}
describe('plugin dependency resolver', () => {
it('orders required dependencies before dependants', () => {
const featurePlugin = manifest('feature.chat', { relationships: { requires: [{ id: 'library.base' }] } });
const result = resolvePluginLoadOrder([{ manifest: featurePlugin }, { manifest: manifest('library.base') }]);
expect(result.blocked).toEqual([]);
expect(result.ordered.map((entry) => entry.id)).toEqual(['library.base', 'feature.chat']);
});
it('uses priority then plugin id for otherwise independent plugins', () => {
const result = resolvePluginLoadOrder([
{ manifest: manifest('plugin.zed') },
{ manifest: manifest('plugin.bootstrap', { load: { priority: 'bootstrap' } }) },
{ manifest: manifest('plugin.alpha') }
]);
expect(result.ordered.map((entry) => entry.id)).toEqual([
'plugin.bootstrap',
'plugin.alpha',
'plugin.zed'
]);
});
it('blocks missing dependencies and leaves valid plugins loadable', () => {
const blockedPlugin = manifest('plugin.blocked', { relationships: { requires: [{ id: 'missing.library' }] } });
const result = resolvePluginLoadOrder([{ manifest: manifest('plugin.valid') }, { manifest: blockedPlugin }]);
expect(result.ordered.map((entry) => entry.id)).toEqual(['plugin.valid']);
expect(result.blocked).toContainEqual({
message: 'Missing required plugin missing.library',
pluginId: 'plugin.blocked',
reason: 'missingDependency'
});
});
it('detects duplicate ids and cycles', () => {
const result = resolvePluginLoadOrder([
{ manifest: manifest('plugin.duplicate') },
{ manifest: manifest('plugin.duplicate') },
{ manifest: manifest('plugin.a', { relationships: { after: ['plugin.b'] } }) },
{ manifest: manifest('plugin.b', { relationships: { after: ['plugin.a'] } }) }
]);
expect(result.blocked).toEqual(expect.arrayContaining([
{
message: 'Duplicate plugin id',
pluginId: 'plugin.duplicate',
reason: 'duplicate'
},
{
message: 'Plugin load order contains a cycle',
pluginId: 'plugin.a',
reason: 'cycle'
},
{
message: 'Plugin load order contains a cycle',
pluginId: 'plugin.b',
reason: 'cycle'
}
]));
});
});