Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

489 lines
15 KiB
TypeScript

/**
* Enhanced Plugin Registry Tests
*
* Tests for version constraints, safe unload, parallel init,
* hot reload, and conflict resolution.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
EnhancedPluginRegistry,
type EnhancedPluginRegistryConfig,
} from '../src/registry/enhanced-plugin-registry.js';
import { PluginBuilder } from '../src/sdk/index.js';
import { BasePlugin } from '../src/core/base-plugin.js';
import type { PluginContext, PluginMetadata, HealthCheckResult } from '../src/types/index.js';
import type { IPlugin } from '../src/core/plugin-interface.js';
// ============================================================================
// Test Helpers
// ============================================================================
function createTestPlugin(
name: string,
version = '1.0.0',
dependencies?: Array<{ name: string; version: string }>
) {
const builder = new PluginBuilder(name, version)
.withDescription(`Test plugin: ${name}`);
if (dependencies) {
builder.withDependencies(dependencies.map(d => `${d.name}@${d.version}`));
}
return builder.build();
}
class TestPlugin extends BasePlugin {
public initializeCalled = false;
public shutdownCalled = false;
public customState: any = null;
constructor(name: string, version: string, deps?: string[]) {
super({
name,
version,
dependencies: deps,
});
}
protected async onInitialize(): Promise<void> {
this.initializeCalled = true;
}
protected async onShutdown(): Promise<void> {
this.shutdownCalled = true;
}
async getState(): Promise<unknown> {
return this.customState;
}
async restoreState(state: unknown): Promise<void> {
this.customState = state;
}
}
function createConfig(overrides?: Partial<EnhancedPluginRegistryConfig>): EnhancedPluginRegistryConfig {
return {
coreVersion: '3.0.0',
dataDir: '/tmp/test',
...overrides,
};
}
// ============================================================================
// Tests
// ============================================================================
describe('EnhancedPluginRegistry', () => {
let registry: EnhancedPluginRegistry;
beforeEach(() => {
registry = new EnhancedPluginRegistry(createConfig());
});
describe('registration', () => {
it('should register a plugin', async () => {
const plugin = createTestPlugin('test-plugin');
await registry.register(plugin);
expect(registry.getPlugin('test-plugin')).toBeDefined();
});
it('should reject duplicate plugins', async () => {
const plugin = createTestPlugin('test-plugin');
await registry.register(plugin);
await expect(registry.register(plugin))
.rejects.toThrow('already registered');
});
it('should respect max plugins limit', async () => {
const limitedRegistry = new EnhancedPluginRegistry(createConfig({ maxPlugins: 2 }));
await limitedRegistry.register(createTestPlugin('plugin-1'));
await limitedRegistry.register(createTestPlugin('plugin-2'));
await expect(limitedRegistry.register(createTestPlugin('plugin-3')))
.rejects.toThrow('Maximum plugin limit');
});
});
describe('version constraints', () => {
it('should check minCoreVersion', async () => {
const plugin = new PluginBuilder('test-plugin', '1.0.0')
.withMinCoreVersion('4.0.0')
.build();
await expect(registry.register(plugin))
.rejects.toThrow('requires core version >= 4.0.0');
});
it('should check maxCoreVersion', async () => {
const plugin = new PluginBuilder('test-plugin', '1.0.0')
.withMinCoreVersion('1.0.0')
.build();
// Should pass since 3.0.0 >= 1.0.0
await registry.register(plugin);
expect(registry.getPlugin('test-plugin')).toBeDefined();
});
it('should validate dependency versions during initialization', async () => {
await registry.register(createTestPlugin('dep-plugin', '1.0.0'));
await registry.register(createTestPlugin('main-plugin', '1.0.0', [
{ name: 'dep-plugin', version: '^2.0.0' }, // Incompatible
]));
await expect(registry.initialize())
.rejects.toThrow('Dependency validation failed');
});
it('should pass validation with compatible versions', async () => {
await registry.register(createTestPlugin('dep-plugin', '1.5.0'));
await registry.register(createTestPlugin('main-plugin', '1.0.0', [
{ name: 'dep-plugin', version: '^1.0.0' },
]));
await registry.initialize();
expect(registry.getStats().initialized).toBe(2);
});
});
describe('safe unload', () => {
it('should prevent unload of plugins with dependents', async () => {
await registry.register(createTestPlugin('base-plugin'));
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
{ name: 'base-plugin', version: '*' },
]));
await registry.initialize();
await expect(registry.unregister('base-plugin'))
.rejects.toThrow('required by');
});
it('should allow cascade unload', async () => {
await registry.register(createTestPlugin('base-plugin'));
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
{ name: 'base-plugin', version: '*' },
]));
await registry.initialize();
await registry.unregister('base-plugin', { cascade: true });
expect(registry.getPlugin('base-plugin')).toBeUndefined();
expect(registry.getPlugin('dependent-plugin')).toBeUndefined();
});
it('should allow force unload', async () => {
await registry.register(createTestPlugin('base-plugin'));
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
{ name: 'base-plugin', version: '*' },
]));
await registry.initialize();
await registry.unregister('base-plugin', { force: true });
expect(registry.getPlugin('base-plugin')).toBeUndefined();
});
});
describe('initialization strategies', () => {
it('should initialize sequentially by default', async () => {
const initOrder: string[] = [];
class OrderTrackingPlugin extends BasePlugin {
constructor(name: string) {
super({ name, version: '1.0.0' });
}
protected async onInitialize(): Promise<void> {
initOrder.push(this.metadata.name);
}
}
const plugin1 = new OrderTrackingPlugin('plugin-1');
const plugin2 = new OrderTrackingPlugin('plugin-2');
await registry.register(plugin1);
await registry.register(plugin2);
await registry.initialize();
expect(initOrder).toHaveLength(2);
});
it('should support parallel initialization', async () => {
const parallelRegistry = new EnhancedPluginRegistry(createConfig({
initializationStrategy: 'parallel',
maxParallelInit: 5,
}));
await parallelRegistry.register(createTestPlugin('plugin-1'));
await parallelRegistry.register(createTestPlugin('plugin-2'));
await parallelRegistry.register(createTestPlugin('plugin-3'));
await parallelRegistry.initialize();
expect(parallelRegistry.getStats().initialized).toBe(3);
});
it('should support parallel-safe initialization', async () => {
const safeRegistry = new EnhancedPluginRegistry(createConfig({
initializationStrategy: 'parallel-safe',
}));
await safeRegistry.register(createTestPlugin('base'));
await safeRegistry.register(createTestPlugin('dep-1', '1.0.0', [
{ name: 'base', version: '*' },
]));
await safeRegistry.register(createTestPlugin('dep-2', '1.0.0', [
{ name: 'base', version: '*' },
]));
await safeRegistry.initialize();
expect(safeRegistry.getStats().initialized).toBe(3);
});
});
describe('hot reload', () => {
it('should reload a plugin', async () => {
const plugin1 = new TestPlugin('test-plugin', '1.0.0');
await registry.register(plugin1);
await registry.initialize();
expect(plugin1.initializeCalled).toBe(true);
const plugin2 = new TestPlugin('test-plugin', '2.0.0');
await registry.reload('test-plugin', plugin2);
expect(plugin1.shutdownCalled).toBe(true);
expect(plugin2.initializeCalled).toBe(true);
expect(registry.getPlugin('test-plugin')?.metadata.version).toBe('2.0.0');
});
it('should preserve state during reload', async () => {
const plugin1 = new TestPlugin('test-plugin', '1.0.0');
plugin1.customState = { counter: 42 };
await registry.register(plugin1);
await registry.initialize();
const plugin2 = new TestPlugin('test-plugin', '2.0.0');
await registry.reload('test-plugin', plugin2, {
preserveState: true,
migrateState: (state: any) => state,
});
expect(plugin2.customState).toEqual({ counter: 42 });
});
it('should reject name mismatch', async () => {
await registry.register(createTestPlugin('original'));
await registry.initialize();
await expect(registry.reload('original', createTestPlugin('different')))
.rejects.toThrow('name mismatch');
});
});
describe('conflict resolution', () => {
it('should error on conflict by default', async () => {
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
.withMCPTools([{
name: 'shared-tool',
description: 'Tool 1',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
}])
.build();
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
.withMCPTools([{
name: 'shared-tool',
description: 'Tool 2',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
}])
.build();
await registry.register(plugin1);
await registry.register(plugin2);
await expect(registry.initialize())
.rejects.toThrow('conflict');
});
it('should support first-wins strategy', async () => {
const conflictRegistry = new EnhancedPluginRegistry(createConfig({
conflictResolution: {
mcpTools: { strategy: 'first' },
},
}));
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
.withMCPTools([{
name: 'shared-tool',
description: 'Tool 1',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
}])
.build();
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
.withMCPTools([{
name: 'shared-tool',
description: 'Tool 2',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
}])
.build();
await conflictRegistry.register(plugin1);
await conflictRegistry.register(plugin2);
await conflictRegistry.initialize();
expect(conflictRegistry.getMCPTools()).toHaveLength(1);
expect(conflictRegistry.getMCPTools()[0].description).toBe('Tool 1');
});
it('should support namespace strategy', async () => {
const namespaceRegistry = new EnhancedPluginRegistry(createConfig({
conflictResolution: {
mcpTools: { strategy: 'namespace', namespaceTemplate: '{plugin}:{name}' },
},
}));
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
.withMCPTools([{
name: 'tool',
description: 'Tool 1',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
}])
.build();
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
.withMCPTools([{
name: 'tool',
description: 'Tool 2',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
}])
.build();
await namespaceRegistry.register(plugin1);
await namespaceRegistry.register(plugin2);
await namespaceRegistry.initialize();
const tools = namespaceRegistry.getMCPTools();
expect(tools).toHaveLength(2);
expect(tools.map(t => t.name)).toContain('plugin-1:tool');
expect(tools.map(t => t.name)).toContain('plugin-2:tool');
});
});
describe('health check', () => {
it('should return health status for all plugins', async () => {
await registry.register(createTestPlugin('healthy-plugin'));
await registry.initialize();
const health = await registry.healthCheck();
expect(health.get('healthy-plugin')?.healthy).toBe(true);
});
it('should report unhealthy for uninitialized plugins', async () => {
await registry.register(createTestPlugin('uninitialized-plugin'));
const health = await registry.healthCheck();
expect(health.get('uninitialized-plugin')?.healthy).toBe(false);
expect(health.get('uninitialized-plugin')?.message).toContain('not initialized');
});
});
describe('enhanced service container', () => {
it('should list all services', async () => {
const services = registry.getServices();
services.set('service-a', { value: 1 });
services.set('service-b', { value: 2 });
const keys = services.list();
expect(keys).toContain('service-a');
expect(keys).toContain('service-b');
expect(keys).toContain('pluginRegistry');
});
it('should list services by prefix', async () => {
const services = registry.getServices();
services.set('db:mysql', {});
services.set('db:postgres', {});
services.set('cache:redis', {});
const dbServices = services.listByPrefix('db:');
expect(dbServices).toHaveLength(2);
expect(dbServices).toContain('db:mysql');
expect(dbServices).toContain('db:postgres');
});
it('should store and retrieve metadata', async () => {
const services = registry.getServices();
services.setWithMetadata('my-service', { active: true }, {
provider: 'test-plugin',
description: 'Test service',
version: '1.0.0',
});
const metadata = services.getMetadata('my-service');
expect(metadata?.provider).toBe('test-plugin');
expect(metadata?.description).toBe('Test service');
});
});
describe('stats', () => {
it('should return correct statistics', async () => {
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
.withMCPTools([{
name: 'tool-1',
description: 'Tool',
inputSchema: { type: 'object', properties: {} },
handler: async () => ({ content: [{ type: 'text', text: '' }] }),
}])
.build();
await registry.register(plugin1);
await registry.register(createTestPlugin('plugin-2'));
await registry.initialize();
const stats = registry.getStats();
expect(stats.total).toBe(2);
expect(stats.initialized).toBe(2);
expect(stats.mcpTools).toBe(1);
});
});
describe('shutdown', () => {
it('should shutdown all plugins', async () => {
const plugin1 = new TestPlugin('plugin-1', '1.0.0');
const plugin2 = new TestPlugin('plugin-2', '1.0.0');
await registry.register(plugin1);
await registry.register(plugin2);
await registry.initialize();
await registry.shutdown();
expect(plugin1.shutdownCalled).toBe(true);
expect(plugin2.shutdownCalled).toBe(true);
});
});
});