import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { ModuleRegistry } from '../app/kernel/module-registry.mjs'; import { ModuleStateStore } from '../app/core/modules/module-state-store.mjs'; import { ModulePackageStore } from '../app/core/modules/module-package-store.mjs'; import { buildModulePackage } from '../app/core/modules/module-package-contract.mjs'; import { ModuleManagerService } from '../app/modules/module-manager/service.mjs'; const failures = []; const assert = (condition, message) => { if (!condition) failures.push(message); }; const expectThrow = async (fn, code, message) => { try { await fn(); failures.push(message); } catch (error) { if (code && error.code !== code) failures.push(`${message} (${error.code || error.message})`); } }; function manifest(id, { requires = [], type = 'feature', status = 'native', owns = [], provides = [], consumes = [] } = {}) { return { schemaVersion: 1, id, name: id, version: '1.0.0', type, status, description: id, requires, permissions: [], provides, consumes, owns }; } const temp = mkdtempSync(join(tmpdir(), 'vendoo-modules-140-')); try { const stateStore = new ModuleStateStore(join(temp, 'config', 'modules-state.json')); const packageStore = new ModulePackageStore(join(temp, 'modules')); const registry = new ModuleRegistry(); registry.register({ manifest: manifest('vendoo.security', { type: 'core', provides: ['security.policies'], owns: ['security.state'] }), lifecycle: { async health(){return {ok:true}} } }); registry.register({ manifest: manifest('vendoo.settings', { type: 'core', requires: ['vendoo.security'], provides: ['settings.configuration'], consumes: ['security.policies'], owns: ['settings.state'] }), lifecycle: { async health(){return {ok:true}} } }); registry.register({ manifest: manifest('vendoo.ai', { requires: ['vendoo.security'], provides: ['ai.images'], consumes: ['security.policies'], owns: ['ai.jobs'] }), lifecycle: { async health(){return {ok:true}} } }); registry.register({ manifest: manifest('vendoo.flux-studio', { requires: ['vendoo.ai'], consumes: ['ai.images'], owns: ['flux.jobs'] }), lifecycle: { async health(){return {ok:true}} } }); const context = { modules: registry, moduleStateStore: stateStore, modulePackageStore: packageStore }; await registry.startAll(context); const service = new ModuleManagerService({ modules: registry, stateStore, packageStore, context }); await expectThrow(() => service.disable('vendoo.security'), 'MODULE_PROTECTED', 'Geschütztes Kernmodul konnte deaktiviert werden'); await service.disable('vendoo.ai', { cascade: true }); assert(!registry.isEnabled('vendoo.ai') && !registry.isEnabled('vendoo.flux-studio'), 'Abhängige optionale Module wurden nicht gemeinsam deaktiviert'); await service.enable('vendoo.flux-studio'); assert(registry.isEnabled('vendoo.ai') && registry.isEnabled('vendoo.flux-studio'), 'Abhängigkeiten wurden beim Aktivieren nicht automatisch gestartet'); assert(stateStore.isEnabled('vendoo.ai') && stateStore.isEnabled('vendoo.flux-studio'), 'Aktivierungsstatus wurde nicht persistiert'); const externalManifest = manifest('vendoo.ext-demo', { type: 'extension-host', owns: ['ext-demo.state'], provides: ['ext-demo.metadata'] }); const packageData = buildModulePackage({ manifest: externalManifest, runtime: { mode: 'declarative', entry: null }, files: [{ path: 'README.md', content: '# Demo\n' }, { path: 'module-ui.json', content: '{"label":"Demo"}\n' }], }); const installed = service.install(Buffer.from(JSON.stringify(packageData), 'utf8')); assert(installed.id === 'vendoo.ext-demo' && installed.source === 'external', 'Deklaratives Fremdmodul wurde nicht installiert'); await service.enable('vendoo.ext-demo'); assert(registry.isEnabled('vendoo.ext-demo'), 'Deklaratives Fremdmodul wurde nicht aktiviert'); const exported = service.export('vendoo.ext-demo'); assert(exported.length > 100 && JSON.parse(exported.toString('utf8')).manifest.id === 'vendoo.ext-demo', 'Fremdmodul-Export ist ungültig'); await service.disable('vendoo.ext-demo'); await service.remove('vendoo.ext-demo'); assert(!registry.has('vendoo.ext-demo') && !packageStore.get('vendoo.ext-demo'), 'Fremdmodul wurde nicht vollständig entfernt'); const executableDraft = { manifest: manifest('vendoo.ext-unsafe', { type: 'extension-host', owns: ['ext-unsafe.state'] }), runtime: { mode: 'declarative', entry: null }, files: [{ path: 'index.mjs', content: 'export default 1;' }], }; await expectThrow(() => Promise.resolve(buildModulePackage(executableDraft)), 'MODULE_PACKAGE_EXECUTABLE_FORBIDDEN', 'Ausführbarer Code wurde in deklarativem Paket akzeptiert'); const isolated = buildModulePackage({ manifest: manifest('vendoo.ext-isolated', { type: 'extension-host', owns: ['ext-isolated.state'] }), runtime: { mode: 'isolated', entry: 'index.mjs' }, files: [{ path: 'index.mjs', content: 'export default {};' }], }); const isolatedInstalled = service.install(Buffer.from(JSON.stringify(isolated), 'utf8')); assert(isolatedInstalled.package?.activatable === false, 'Unsigniertes ausführbares Modul ist aktivierbar'); await expectThrow(() => service.enable('vendoo.ext-isolated'), 'MODULE_NOT_ACTIVATABLE', 'Unsigniertes ausführbares Modul wurde aktiviert'); const stateFile = JSON.parse(readFileSync(join(temp, 'config', 'modules-state.json'), 'utf8')); assert(stateFile.schemaVersion === 1 && stateFile.modules['vendoo.ai'], 'Persistente Modulstatusdatei ist unvollständig'); assert(service.catalog().summary.total >= 5, 'Modulkatalog ist unvollständig'); } finally { rmSync(temp, { recursive: true, force: true }); } if (failures.length) { console.error(`Module-Manager-Gate 1.41.1 fehlgeschlagen (${failures.length}):`); failures.forEach(item => console.error(`- ${item}`)); process.exit(1); } console.log('Module-Manager-Gate 1.41.1 erfolgreich: Kernschutz, Abhängigkeitskaskade, Persistenz, .vmod-Import/Export, Entfernung und Quarantäne geprüft.');