@sparkleideas/plugins 3.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Plugin Registry Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for version constraints, safe unload, parallel init,
|
|
5
|
+
* hot reload, and conflict resolution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
EnhancedPluginRegistry,
|
|
11
|
+
type EnhancedPluginRegistryConfig,
|
|
12
|
+
} from '../src/registry/enhanced-plugin-registry.js';
|
|
13
|
+
import { PluginBuilder } from '../src/sdk/index.js';
|
|
14
|
+
import { BasePlugin } from '../src/core/base-plugin.js';
|
|
15
|
+
import type { PluginContext, PluginMetadata, HealthCheckResult } from '../src/types/index.js';
|
|
16
|
+
import type { IPlugin } from '../src/core/plugin-interface.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Test Helpers
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
function createTestPlugin(
|
|
23
|
+
name: string,
|
|
24
|
+
version = '1.0.0',
|
|
25
|
+
dependencies?: Array<{ name: string; version: string }>
|
|
26
|
+
) {
|
|
27
|
+
const builder = new PluginBuilder(name, version)
|
|
28
|
+
.withDescription(`Test plugin: ${name}`);
|
|
29
|
+
|
|
30
|
+
if (dependencies) {
|
|
31
|
+
builder.withDependencies(dependencies);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return builder.build();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class TestPlugin extends BasePlugin {
|
|
38
|
+
public initializeCalled = false;
|
|
39
|
+
public shutdownCalled = false;
|
|
40
|
+
public state: any = null;
|
|
41
|
+
|
|
42
|
+
constructor(name: string, version: string, deps?: string[]) {
|
|
43
|
+
super({
|
|
44
|
+
name,
|
|
45
|
+
version,
|
|
46
|
+
dependencies: deps,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected async onInitialize(): Promise<void> {
|
|
51
|
+
this.initializeCalled = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async onShutdown(): Promise<void> {
|
|
55
|
+
this.shutdownCalled = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getState(): Promise<unknown> {
|
|
59
|
+
return this.state;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async setState(state: unknown): Promise<void> {
|
|
63
|
+
this.state = state;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createConfig(overrides?: Partial<EnhancedPluginRegistryConfig>): EnhancedPluginRegistryConfig {
|
|
68
|
+
return {
|
|
69
|
+
coreVersion: '3.0.0',
|
|
70
|
+
dataDir: '/tmp/test',
|
|
71
|
+
...overrides,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Tests
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
describe('EnhancedPluginRegistry', () => {
|
|
80
|
+
let registry: EnhancedPluginRegistry;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
registry = new EnhancedPluginRegistry(createConfig());
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('registration', () => {
|
|
87
|
+
it('should register a plugin', async () => {
|
|
88
|
+
const plugin = createTestPlugin('test-plugin');
|
|
89
|
+
|
|
90
|
+
await registry.register(plugin);
|
|
91
|
+
|
|
92
|
+
expect(registry.getPlugin('test-plugin')).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should reject duplicate plugins', async () => {
|
|
96
|
+
const plugin = createTestPlugin('test-plugin');
|
|
97
|
+
|
|
98
|
+
await registry.register(plugin);
|
|
99
|
+
|
|
100
|
+
await expect(registry.register(plugin))
|
|
101
|
+
.rejects.toThrow('already registered');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should respect max plugins limit', async () => {
|
|
105
|
+
const limitedRegistry = new EnhancedPluginRegistry(createConfig({ maxPlugins: 2 }));
|
|
106
|
+
|
|
107
|
+
await limitedRegistry.register(createTestPlugin('plugin-1'));
|
|
108
|
+
await limitedRegistry.register(createTestPlugin('plugin-2'));
|
|
109
|
+
|
|
110
|
+
await expect(limitedRegistry.register(createTestPlugin('plugin-3')))
|
|
111
|
+
.rejects.toThrow('Maximum plugin limit');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('version constraints', () => {
|
|
116
|
+
it('should check minCoreVersion', async () => {
|
|
117
|
+
const plugin = new PluginBuilder('test-plugin', '1.0.0')
|
|
118
|
+
.withMinCoreVersion('4.0.0')
|
|
119
|
+
.build();
|
|
120
|
+
|
|
121
|
+
await expect(registry.register(plugin))
|
|
122
|
+
.rejects.toThrow('requires core version >= 4.0.0');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should check maxCoreVersion', async () => {
|
|
126
|
+
const plugin = new PluginBuilder('test-plugin', '1.0.0')
|
|
127
|
+
.withMinCoreVersion('1.0.0')
|
|
128
|
+
.build();
|
|
129
|
+
|
|
130
|
+
// Should pass since 3.0.0 >= 1.0.0
|
|
131
|
+
await registry.register(plugin);
|
|
132
|
+
expect(registry.getPlugin('test-plugin')).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should validate dependency versions during initialization', async () => {
|
|
136
|
+
await registry.register(createTestPlugin('dep-plugin', '1.0.0'));
|
|
137
|
+
await registry.register(createTestPlugin('main-plugin', '1.0.0', [
|
|
138
|
+
{ name: 'dep-plugin', version: '^2.0.0' }, // Incompatible
|
|
139
|
+
]));
|
|
140
|
+
|
|
141
|
+
await expect(registry.initialize())
|
|
142
|
+
.rejects.toThrow('Dependency validation failed');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should pass validation with compatible versions', async () => {
|
|
146
|
+
await registry.register(createTestPlugin('dep-plugin', '1.5.0'));
|
|
147
|
+
await registry.register(createTestPlugin('main-plugin', '1.0.0', [
|
|
148
|
+
{ name: 'dep-plugin', version: '^1.0.0' },
|
|
149
|
+
]));
|
|
150
|
+
|
|
151
|
+
await registry.initialize();
|
|
152
|
+
|
|
153
|
+
expect(registry.getStats().initialized).toBe(2);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('safe unload', () => {
|
|
158
|
+
it('should prevent unload of plugins with dependents', async () => {
|
|
159
|
+
await registry.register(createTestPlugin('base-plugin'));
|
|
160
|
+
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
|
|
161
|
+
{ name: 'base-plugin', version: '*' },
|
|
162
|
+
]));
|
|
163
|
+
await registry.initialize();
|
|
164
|
+
|
|
165
|
+
await expect(registry.unregister('base-plugin'))
|
|
166
|
+
.rejects.toThrow('required by');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should allow cascade unload', async () => {
|
|
170
|
+
await registry.register(createTestPlugin('base-plugin'));
|
|
171
|
+
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
|
|
172
|
+
{ name: 'base-plugin', version: '*' },
|
|
173
|
+
]));
|
|
174
|
+
await registry.initialize();
|
|
175
|
+
|
|
176
|
+
await registry.unregister('base-plugin', { cascade: true });
|
|
177
|
+
|
|
178
|
+
expect(registry.getPlugin('base-plugin')).toBeUndefined();
|
|
179
|
+
expect(registry.getPlugin('dependent-plugin')).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should allow force unload', async () => {
|
|
183
|
+
await registry.register(createTestPlugin('base-plugin'));
|
|
184
|
+
await registry.register(createTestPlugin('dependent-plugin', '1.0.0', [
|
|
185
|
+
{ name: 'base-plugin', version: '*' },
|
|
186
|
+
]));
|
|
187
|
+
await registry.initialize();
|
|
188
|
+
|
|
189
|
+
await registry.unregister('base-plugin', { force: true });
|
|
190
|
+
|
|
191
|
+
expect(registry.getPlugin('base-plugin')).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('initialization strategies', () => {
|
|
196
|
+
it('should initialize sequentially by default', async () => {
|
|
197
|
+
const initOrder: string[] = [];
|
|
198
|
+
|
|
199
|
+
class OrderTrackingPlugin extends BasePlugin {
|
|
200
|
+
constructor(name: string) {
|
|
201
|
+
super({ name, version: '1.0.0' });
|
|
202
|
+
}
|
|
203
|
+
protected async onInitialize(): Promise<void> {
|
|
204
|
+
initOrder.push(this.metadata.name);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const plugin1 = new OrderTrackingPlugin('plugin-1');
|
|
209
|
+
const plugin2 = new OrderTrackingPlugin('plugin-2');
|
|
210
|
+
|
|
211
|
+
await registry.register(plugin1);
|
|
212
|
+
await registry.register(plugin2);
|
|
213
|
+
await registry.initialize();
|
|
214
|
+
|
|
215
|
+
expect(initOrder).toHaveLength(2);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should support parallel initialization', async () => {
|
|
219
|
+
const parallelRegistry = new EnhancedPluginRegistry(createConfig({
|
|
220
|
+
initializationStrategy: 'parallel',
|
|
221
|
+
maxParallelInit: 5,
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
await parallelRegistry.register(createTestPlugin('plugin-1'));
|
|
225
|
+
await parallelRegistry.register(createTestPlugin('plugin-2'));
|
|
226
|
+
await parallelRegistry.register(createTestPlugin('plugin-3'));
|
|
227
|
+
|
|
228
|
+
await parallelRegistry.initialize();
|
|
229
|
+
|
|
230
|
+
expect(parallelRegistry.getStats().initialized).toBe(3);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should support parallel-safe initialization', async () => {
|
|
234
|
+
const safeRegistry = new EnhancedPluginRegistry(createConfig({
|
|
235
|
+
initializationStrategy: 'parallel-safe',
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
await safeRegistry.register(createTestPlugin('base'));
|
|
239
|
+
await safeRegistry.register(createTestPlugin('dep-1', '1.0.0', [
|
|
240
|
+
{ name: 'base', version: '*' },
|
|
241
|
+
]));
|
|
242
|
+
await safeRegistry.register(createTestPlugin('dep-2', '1.0.0', [
|
|
243
|
+
{ name: 'base', version: '*' },
|
|
244
|
+
]));
|
|
245
|
+
|
|
246
|
+
await safeRegistry.initialize();
|
|
247
|
+
|
|
248
|
+
expect(safeRegistry.getStats().initialized).toBe(3);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('hot reload', () => {
|
|
253
|
+
it('should reload a plugin', async () => {
|
|
254
|
+
const plugin1 = new TestPlugin('test-plugin', '1.0.0');
|
|
255
|
+
await registry.register(plugin1);
|
|
256
|
+
await registry.initialize();
|
|
257
|
+
|
|
258
|
+
expect(plugin1.initializeCalled).toBe(true);
|
|
259
|
+
|
|
260
|
+
const plugin2 = new TestPlugin('test-plugin', '2.0.0');
|
|
261
|
+
await registry.reload('test-plugin', plugin2);
|
|
262
|
+
|
|
263
|
+
expect(plugin1.shutdownCalled).toBe(true);
|
|
264
|
+
expect(plugin2.initializeCalled).toBe(true);
|
|
265
|
+
expect(registry.getPlugin('test-plugin')?.metadata.version).toBe('2.0.0');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should preserve state during reload', async () => {
|
|
269
|
+
const plugin1 = new TestPlugin('test-plugin', '1.0.0');
|
|
270
|
+
plugin1.state = { counter: 42 };
|
|
271
|
+
|
|
272
|
+
await registry.register(plugin1);
|
|
273
|
+
await registry.initialize();
|
|
274
|
+
|
|
275
|
+
const plugin2 = new TestPlugin('test-plugin', '2.0.0');
|
|
276
|
+
|
|
277
|
+
await registry.reload('test-plugin', plugin2, {
|
|
278
|
+
preserveState: true,
|
|
279
|
+
migrateState: (state: any) => state,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(plugin2.state).toEqual({ counter: 42 });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should reject name mismatch', async () => {
|
|
286
|
+
await registry.register(createTestPlugin('original'));
|
|
287
|
+
await registry.initialize();
|
|
288
|
+
|
|
289
|
+
await expect(registry.reload('original', createTestPlugin('different')))
|
|
290
|
+
.rejects.toThrow('name mismatch');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('conflict resolution', () => {
|
|
295
|
+
it('should error on conflict by default', async () => {
|
|
296
|
+
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
|
|
297
|
+
.withMCPTools([{
|
|
298
|
+
name: 'shared-tool',
|
|
299
|
+
description: 'Tool 1',
|
|
300
|
+
inputSchema: { type: 'object', properties: {} },
|
|
301
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
|
|
302
|
+
}])
|
|
303
|
+
.build();
|
|
304
|
+
|
|
305
|
+
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
|
|
306
|
+
.withMCPTools([{
|
|
307
|
+
name: 'shared-tool',
|
|
308
|
+
description: 'Tool 2',
|
|
309
|
+
inputSchema: { type: 'object', properties: {} },
|
|
310
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
|
|
311
|
+
}])
|
|
312
|
+
.build();
|
|
313
|
+
|
|
314
|
+
await registry.register(plugin1);
|
|
315
|
+
await registry.register(plugin2);
|
|
316
|
+
|
|
317
|
+
await expect(registry.initialize())
|
|
318
|
+
.rejects.toThrow('conflict');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should support first-wins strategy', async () => {
|
|
322
|
+
const conflictRegistry = new EnhancedPluginRegistry(createConfig({
|
|
323
|
+
conflictResolution: {
|
|
324
|
+
mcpTools: { strategy: 'first' },
|
|
325
|
+
},
|
|
326
|
+
}));
|
|
327
|
+
|
|
328
|
+
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
|
|
329
|
+
.withMCPTools([{
|
|
330
|
+
name: 'shared-tool',
|
|
331
|
+
description: 'Tool 1',
|
|
332
|
+
inputSchema: { type: 'object', properties: {} },
|
|
333
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
|
|
334
|
+
}])
|
|
335
|
+
.build();
|
|
336
|
+
|
|
337
|
+
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
|
|
338
|
+
.withMCPTools([{
|
|
339
|
+
name: 'shared-tool',
|
|
340
|
+
description: 'Tool 2',
|
|
341
|
+
inputSchema: { type: 'object', properties: {} },
|
|
342
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
|
|
343
|
+
}])
|
|
344
|
+
.build();
|
|
345
|
+
|
|
346
|
+
await conflictRegistry.register(plugin1);
|
|
347
|
+
await conflictRegistry.register(plugin2);
|
|
348
|
+
await conflictRegistry.initialize();
|
|
349
|
+
|
|
350
|
+
expect(conflictRegistry.getMCPTools()).toHaveLength(1);
|
|
351
|
+
expect(conflictRegistry.getMCPTools()[0].description).toBe('Tool 1');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should support namespace strategy', async () => {
|
|
355
|
+
const namespaceRegistry = new EnhancedPluginRegistry(createConfig({
|
|
356
|
+
conflictResolution: {
|
|
357
|
+
mcpTools: { strategy: 'namespace', namespaceTemplate: '{plugin}:{name}' },
|
|
358
|
+
},
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
|
|
362
|
+
.withMCPTools([{
|
|
363
|
+
name: 'tool',
|
|
364
|
+
description: 'Tool 1',
|
|
365
|
+
inputSchema: { type: 'object', properties: {} },
|
|
366
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v1' }] }),
|
|
367
|
+
}])
|
|
368
|
+
.build();
|
|
369
|
+
|
|
370
|
+
const plugin2 = new PluginBuilder('plugin-2', '1.0.0')
|
|
371
|
+
.withMCPTools([{
|
|
372
|
+
name: 'tool',
|
|
373
|
+
description: 'Tool 2',
|
|
374
|
+
inputSchema: { type: 'object', properties: {} },
|
|
375
|
+
handler: async () => ({ content: [{ type: 'text', text: 'v2' }] }),
|
|
376
|
+
}])
|
|
377
|
+
.build();
|
|
378
|
+
|
|
379
|
+
await namespaceRegistry.register(plugin1);
|
|
380
|
+
await namespaceRegistry.register(plugin2);
|
|
381
|
+
await namespaceRegistry.initialize();
|
|
382
|
+
|
|
383
|
+
const tools = namespaceRegistry.getMCPTools();
|
|
384
|
+
expect(tools).toHaveLength(2);
|
|
385
|
+
expect(tools.map(t => t.name)).toContain('plugin-1:tool');
|
|
386
|
+
expect(tools.map(t => t.name)).toContain('plugin-2:tool');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('health check', () => {
|
|
391
|
+
it('should return health status for all plugins', async () => {
|
|
392
|
+
await registry.register(createTestPlugin('healthy-plugin'));
|
|
393
|
+
await registry.initialize();
|
|
394
|
+
|
|
395
|
+
const health = await registry.healthCheck();
|
|
396
|
+
|
|
397
|
+
expect(health.get('healthy-plugin')?.healthy).toBe(true);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should report unhealthy for uninitialized plugins', async () => {
|
|
401
|
+
await registry.register(createTestPlugin('uninitialized-plugin'));
|
|
402
|
+
|
|
403
|
+
const health = await registry.healthCheck();
|
|
404
|
+
|
|
405
|
+
expect(health.get('uninitialized-plugin')?.healthy).toBe(false);
|
|
406
|
+
expect(health.get('uninitialized-plugin')?.message).toContain('not initialized');
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe('enhanced service container', () => {
|
|
411
|
+
it('should list all services', async () => {
|
|
412
|
+
const services = registry.getServices();
|
|
413
|
+
|
|
414
|
+
services.set('service-a', { value: 1 });
|
|
415
|
+
services.set('service-b', { value: 2 });
|
|
416
|
+
|
|
417
|
+
const keys = services.list();
|
|
418
|
+
expect(keys).toContain('service-a');
|
|
419
|
+
expect(keys).toContain('service-b');
|
|
420
|
+
expect(keys).toContain('pluginRegistry');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should list services by prefix', async () => {
|
|
424
|
+
const services = registry.getServices();
|
|
425
|
+
|
|
426
|
+
services.set('db:mysql', {});
|
|
427
|
+
services.set('db:postgres', {});
|
|
428
|
+
services.set('cache:redis', {});
|
|
429
|
+
|
|
430
|
+
const dbServices = services.listByPrefix('db:');
|
|
431
|
+
expect(dbServices).toHaveLength(2);
|
|
432
|
+
expect(dbServices).toContain('db:mysql');
|
|
433
|
+
expect(dbServices).toContain('db:postgres');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should store and retrieve metadata', async () => {
|
|
437
|
+
const services = registry.getServices();
|
|
438
|
+
|
|
439
|
+
services.setWithMetadata('my-service', { active: true }, {
|
|
440
|
+
provider: 'test-plugin',
|
|
441
|
+
description: 'Test service',
|
|
442
|
+
version: '1.0.0',
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const metadata = services.getMetadata('my-service');
|
|
446
|
+
expect(metadata?.provider).toBe('test-plugin');
|
|
447
|
+
expect(metadata?.description).toBe('Test service');
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe('stats', () => {
|
|
452
|
+
it('should return correct statistics', async () => {
|
|
453
|
+
const plugin1 = new PluginBuilder('plugin-1', '1.0.0')
|
|
454
|
+
.withMCPTools([{
|
|
455
|
+
name: 'tool-1',
|
|
456
|
+
description: 'Tool',
|
|
457
|
+
inputSchema: { type: 'object', properties: {} },
|
|
458
|
+
handler: async () => ({ content: [{ type: 'text', text: '' }] }),
|
|
459
|
+
}])
|
|
460
|
+
.build();
|
|
461
|
+
|
|
462
|
+
await registry.register(plugin1);
|
|
463
|
+
await registry.register(createTestPlugin('plugin-2'));
|
|
464
|
+
await registry.initialize();
|
|
465
|
+
|
|
466
|
+
const stats = registry.getStats();
|
|
467
|
+
|
|
468
|
+
expect(stats.total).toBe(2);
|
|
469
|
+
expect(stats.initialized).toBe(2);
|
|
470
|
+
expect(stats.mcpTools).toBe(1);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('shutdown', () => {
|
|
475
|
+
it('should shutdown all plugins', async () => {
|
|
476
|
+
const plugin1 = new TestPlugin('plugin-1', '1.0.0');
|
|
477
|
+
const plugin2 = new TestPlugin('plugin-2', '1.0.0');
|
|
478
|
+
|
|
479
|
+
await registry.register(plugin1);
|
|
480
|
+
await registry.register(plugin2);
|
|
481
|
+
await registry.initialize();
|
|
482
|
+
await registry.shutdown();
|
|
483
|
+
|
|
484
|
+
expect(plugin1.shutdownCalled).toBe(true);
|
|
485
|
+
expect(plugin2.shutdownCalled).toBe(true);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|