/** * DC-080: Plugin manager tests */ const fs = require('fs'); const path = require('path'); const os = require('os'); const { PluginManager } = require('../../src/plugins/plugin-manager'); describe('DC-080: Plugin Manager', () => { let tmpDir, manager; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dc-plugins-')); manager = new PluginManager({ dataDir: tmpDir, log: { info: jest.fn(), error: jest.fn() }, }); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); describe('loadAll()', () => { it('creates plugin directory if it does not exist', async () => { const pluginDir = path.join(tmpDir, 'plugins'); expect(fs.existsSync(pluginDir)).toBe(false); await manager.loadAll(); expect(fs.existsSync(pluginDir)).toBe(true); }); it('loads successfully with empty plugin dir', async () => { await manager.loadAll(); expect(manager.plugins.size).toBe(0); expect(manager.loaded).toBe(true); }); it('skips hidden directories', async () => { const hiddenDir = path.join(tmpDir, 'plugins', '.hidden'); fs.mkdirSync(hiddenDir, { recursive: true }); await manager.loadAll(); expect(manager.plugins.size).toBe(0); }); }); describe('loadOne()', () => { it('loads a plugin with valid manifest', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'test-plugin'); fs.mkdirSync(pluginDir, { recursive: true }); fs.writeFileSync( path.join(pluginDir, 'manifest.json'), JSON.stringify({ name: 'test-plugin', version: '1.0.0', description: 'A test plugin', }) ); await manager.loadOne(pluginDir); expect(manager.plugins.has('test-plugin')).toBe(true); }); it('throws if manifest.json is missing', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'no-manifest'); fs.mkdirSync(pluginDir, { recursive: true }); await expect(manager.loadOne(pluginDir)).rejects.toThrow('manifest.json'); }); it('throws if manifest lacks name or version', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'invalid'); fs.mkdirSync(pluginDir, { recursive: true }); fs.writeFileSync( path.join(pluginDir, 'manifest.json'), JSON.stringify({ description: 'no name' }) ); await expect(manager.loadOne(pluginDir)).rejects.toThrow('name and version'); }); it('throws on duplicate plugin name', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'dup'); fs.mkdirSync(pluginDir, { recursive: true }); fs.writeFileSync( path.join(pluginDir, 'manifest.json'), JSON.stringify({ name: 'dup', version: '1.0.0' }) ); await manager.loadOne(pluginDir); await expect(manager.loadOne(pluginDir)).rejects.toThrow('already loaded'); }); }); describe('unload()', () => { it('unloads a loaded plugin', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'removable'); fs.mkdirSync(pluginDir, { recursive: true }); fs.writeFileSync( path.join(pluginDir, 'manifest.json'), JSON.stringify({ name: 'removable', version: '1.0.0' }) ); await manager.loadOne(pluginDir); expect(manager.plugins.has('removable')).toBe(true); manager.unload('removable'); expect(manager.plugins.has('removable')).toBe(false); }); it('returns false for unknown plugin', () => { expect(manager.unload('nonexistent')).toBe(false); }); }); describe('list()', () => { it('returns empty array when no plugins', () => { expect(manager.list()).toEqual([]); }); it('returns plugin metadata', async () => { const pluginDir = path.join(tmpDir, 'plugins', 'listed'); fs.mkdirSync(pluginDir, { recursive: true }); fs.writeFileSync( path.join(pluginDir, 'manifest.json'), JSON.stringify({ name: 'listed', version: '2.0.0', description: 'Test' }) ); await manager.loadOne(pluginDir); const list = manager.list(); expect(list).toHaveLength(1); expect(list[0].name).toBe('listed'); expect(list[0].version).toBe('2.0.0'); }); }); describe('executeHook()', () => { it('returns empty results when no plugins have the hook', async () => { await manager.loadAll(); const results = await manager.executeHook('service:health-check'); expect(results).toEqual([]); }); }); describe('getWidgets()', () => { it('returns empty array by default', () => { expect(manager.getWidgets()).toEqual([]); }); }); describe('getServiceTypes()', () => { it('returns empty array by default', () => { expect(manager.getServiceTypes()).toEqual([]); }); }); });