- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
343 lines
9.4 KiB
JavaScript
343 lines
9.4 KiB
JavaScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
import { DependencyManager } from '../../resources/js/core/DependencyManager.js';
|
|
|
|
// Mock Logger
|
|
jest.mock('../../resources/js/core/logger.js', () => ({
|
|
Logger: {
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
error: jest.fn()
|
|
}
|
|
}));
|
|
|
|
describe('DependencyManager', () => {
|
|
let depManager;
|
|
|
|
beforeEach(() => {
|
|
depManager = new DependencyManager();
|
|
});
|
|
|
|
describe('Module registration', () => {
|
|
test('should register module with valid definition', () => {
|
|
const definition = {
|
|
name: 'test-module',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: ['test-service'],
|
|
priority: 0
|
|
};
|
|
|
|
depManager.register(definition);
|
|
|
|
expect(depManager.getModule('test-module')).toEqual(definition);
|
|
});
|
|
|
|
test('should reject invalid module definitions', () => {
|
|
const invalidDefinitions = [
|
|
{ version: '1.0.0', dependencies: [] }, // No name
|
|
{ name: 'test', dependencies: [] }, // No version
|
|
{ name: 'test', version: '1.0.0' }, // No dependencies array
|
|
{ name: 'test', version: '1.0.0', dependencies: [{}] } // Invalid dependency
|
|
];
|
|
|
|
invalidDefinitions.forEach(def => {
|
|
depManager.register(def);
|
|
expect(depManager.getModule(def.name)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
test('should not allow duplicate registration', () => {
|
|
const definition1 = {
|
|
name: 'duplicate',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
};
|
|
|
|
const definition2 = {
|
|
name: 'duplicate',
|
|
version: '2.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
};
|
|
|
|
depManager.register(definition1);
|
|
depManager.register(definition2);
|
|
|
|
expect(depManager.getModule('duplicate').version).toBe('1.0.0');
|
|
});
|
|
|
|
test('should build dependents map correctly', () => {
|
|
const moduleA = {
|
|
name: 'module-a',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
};
|
|
|
|
const moduleB = {
|
|
name: 'module-b',
|
|
version: '1.0.0',
|
|
dependencies: [{ name: 'module-a', version: '1.0.0', optional: false }],
|
|
provides: [],
|
|
priority: 0
|
|
};
|
|
|
|
depManager.register(moduleA);
|
|
depManager.register(moduleB);
|
|
|
|
expect(depManager.getDependents('module-a')).toContain('module-b');
|
|
});
|
|
});
|
|
|
|
describe('Dependency resolution', () => {
|
|
beforeEach(() => {
|
|
// Register test modules
|
|
depManager.register({
|
|
name: 'core',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: ['core-service'],
|
|
priority: 100
|
|
});
|
|
|
|
depManager.register({
|
|
name: 'ui',
|
|
version: '1.0.0',
|
|
dependencies: [{ name: 'core', version: '1.0.0', optional: false }],
|
|
provides: ['ui-service'],
|
|
priority: 50
|
|
});
|
|
|
|
depManager.register({
|
|
name: 'advanced',
|
|
version: '1.0.0',
|
|
dependencies: [
|
|
{ name: 'core', version: '1.0.0', optional: false },
|
|
{ name: 'ui', version: '1.0.0', optional: false }
|
|
],
|
|
provides: ['advanced-service'],
|
|
priority: 10
|
|
});
|
|
});
|
|
|
|
test('should calculate correct initialization order', () => {
|
|
const order = depManager.calculateInitializationOrder();
|
|
|
|
expect(order).toEqual(['core', 'ui', 'advanced']);
|
|
});
|
|
|
|
test('should handle optional dependencies', () => {
|
|
depManager.register({
|
|
name: 'optional-dep',
|
|
version: '1.0.0',
|
|
dependencies: [
|
|
{ name: 'core', version: '1.0.0', optional: false },
|
|
{ name: 'missing-optional', version: '1.0.0', optional: true }
|
|
],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
const order = depManager.calculateInitializationOrder();
|
|
|
|
expect(order).toContain('optional-dep');
|
|
});
|
|
|
|
test('should detect circular dependencies', () => {
|
|
depManager.register({
|
|
name: 'circular-a',
|
|
version: '1.0.0',
|
|
dependencies: [{ name: 'circular-b', version: '1.0.0', optional: false }],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
depManager.register({
|
|
name: 'circular-b',
|
|
version: '1.0.0',
|
|
dependencies: [{ name: 'circular-a', version: '1.0.0', optional: false }],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
expect(() => {
|
|
depManager.calculateInitializationOrder();
|
|
}).toThrow('Circular dependency detected');
|
|
});
|
|
|
|
test('should respect priority in initialization order', () => {
|
|
depManager.register({
|
|
name: 'high-priority',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 200
|
|
});
|
|
|
|
const order = depManager.calculateInitializationOrder();
|
|
|
|
expect(order.indexOf('high-priority')).toBeLessThan(order.indexOf('core'));
|
|
});
|
|
});
|
|
|
|
describe('Dependency checking', () => {
|
|
beforeEach(() => {
|
|
depManager.register({
|
|
name: 'dependency',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
depManager.register({
|
|
name: 'dependent',
|
|
version: '1.0.0',
|
|
dependencies: [{ name: 'dependency', version: '1.0.0', optional: false }],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
});
|
|
|
|
test('should check dependencies correctly', () => {
|
|
const check = depManager.checkDependencies('dependent');
|
|
|
|
expect(check.satisfied).toBe(false);
|
|
expect(check.missing).toContain('dependency (not initialized)');
|
|
});
|
|
|
|
test('should pass dependency check when dependencies are initialized', () => {
|
|
depManager.markInitialized('dependency');
|
|
const check = depManager.checkDependencies('dependent');
|
|
|
|
expect(check.satisfied).toBe(true);
|
|
expect(check.missing).toHaveLength(0);
|
|
});
|
|
|
|
test('should handle optional dependencies in check', () => {
|
|
depManager.register({
|
|
name: 'optional-user',
|
|
version: '1.0.0',
|
|
dependencies: [
|
|
{ name: 'dependency', version: '1.0.0', optional: false },
|
|
{ name: 'missing-optional', version: '1.0.0', optional: true }
|
|
],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
depManager.markInitialized('dependency');
|
|
const check = depManager.checkDependencies('optional-user');
|
|
|
|
expect(check.satisfied).toBe(true);
|
|
expect(check.optional).toContain('missing-optional');
|
|
});
|
|
|
|
test('should check readiness for initialization', () => {
|
|
expect(depManager.isReadyToInitialize('dependency')).toBe(true);
|
|
expect(depManager.isReadyToInitialize('dependent')).toBe(false);
|
|
|
|
depManager.markInitialized('dependency');
|
|
expect(depManager.isReadyToInitialize('dependent')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Module state tracking', () => {
|
|
beforeEach(() => {
|
|
depManager.register({
|
|
name: 'test-module',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
});
|
|
|
|
test('should track initialization state', () => {
|
|
expect(depManager.initialized.has('test-module')).toBe(false);
|
|
expect(depManager.initializing.has('test-module')).toBe(false);
|
|
|
|
depManager.markInitializing('test-module');
|
|
expect(depManager.initializing.has('test-module')).toBe(true);
|
|
|
|
depManager.markInitialized('test-module');
|
|
expect(depManager.initialized.has('test-module')).toBe(true);
|
|
expect(depManager.initializing.has('test-module')).toBe(false);
|
|
});
|
|
|
|
test('should provide status report', () => {
|
|
depManager.register({
|
|
name: 'module-2',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
depManager.markInitialized('test-module');
|
|
depManager.markInitializing('module-2');
|
|
|
|
const status = depManager.getStatus();
|
|
|
|
expect(status.total).toBe(2);
|
|
expect(status.initialized).toBe(1);
|
|
expect(status.initializing).toBe(1);
|
|
expect(status.pending).toBe(0);
|
|
expect(status.modules.initialized).toContain('test-module');
|
|
expect(status.modules.initializing).toContain('module-2');
|
|
});
|
|
});
|
|
|
|
describe('Definition builder', () => {
|
|
test('should create definition with builder pattern', () => {
|
|
const definition = DependencyManager.createDefinition('builder-test', '2.0.0')
|
|
.depends('dep1', '1.0.0')
|
|
.depends('dep2', '2.0.0', true)
|
|
.provides('service1', 'service2')
|
|
.priority(50);
|
|
|
|
expect(definition.name).toBe('builder-test');
|
|
expect(definition.version).toBe('2.0.0');
|
|
expect(definition.dependencies).toHaveLength(2);
|
|
expect(definition.dependencies[0]).toEqual({
|
|
name: 'dep1',
|
|
version: '1.0.0',
|
|
optional: false
|
|
});
|
|
expect(definition.dependencies[1]).toEqual({
|
|
name: 'dep2',
|
|
version: '2.0.0',
|
|
optional: true
|
|
});
|
|
expect(definition.provides).toEqual(['service1', 'service2']);
|
|
expect(definition.priority).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe('Reset and cleanup', () => {
|
|
test('should reset all state', () => {
|
|
depManager.register({
|
|
name: 'test',
|
|
version: '1.0.0',
|
|
dependencies: [],
|
|
provides: [],
|
|
priority: 0
|
|
});
|
|
|
|
depManager.markInitialized('test');
|
|
|
|
depManager.reset();
|
|
|
|
expect(depManager.modules.size).toBe(0);
|
|
expect(depManager.initialized.size).toBe(0);
|
|
expect(depManager.dependents.size).toBe(0);
|
|
});
|
|
});
|
|
}); |