Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,343 @@
/**
* @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);
});
});
});

173
tests/core/Logger.test.js Normal file
View File

@@ -0,0 +1,173 @@
/**
* @jest-environment jsdom
*/
import { Logger } from '../../resources/js/core/logger.js';
// Mock the frameloop dependency
jest.mock('../../resources/js/core/frameloop.js', () => ({
monitor: {
log: jest.fn()
}
}));
import { monitor } from '../../resources/js/core/frameloop.js';
describe('Logger', () => {
beforeEach(() => {
Logger.enabled = true;
jest.clearAllMocks();
});
describe('Basic logging functionality', () => {
test('should log messages when enabled', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
Logger.log('test message');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('[LOG]')
);
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('test message')
);
consoleSpy.mockRestore();
});
test('should not log messages when disabled', () => {
Logger.enabled = false;
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
Logger.info('test message');
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
test('should handle different log levels', () => {
const methods = ['log', 'warn', 'info', 'error'];
methods.forEach(method => {
const consoleSpy = jest.spyOn(console, method).mockImplementation();
Logger[method]('test message');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining(`[${method.toUpperCase()}]`)
);
consoleSpy.mockRestore();
});
});
test('should format timestamps correctly', () => {
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
Logger.info('test message');
const logCall = consoleSpy.mock.calls[0][0];
expect(logCall).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
consoleSpy.mockRestore();
});
test('should stringify objects', () => {
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
const testObj = { key: 'value', number: 42 };
Logger.info('Object:', testObj);
const logCall = consoleSpy.mock.calls[0][0];
expect(logCall).toContain('{"key":"value","number":42}');
consoleSpy.mockRestore();
});
test('should handle mixed argument types', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
Logger.log('Message:', 42, { key: 'value' }, true);
const logCall = consoleSpy.mock.calls[0][0];
expect(logCall).toContain('Message: 42 {"key":"value"} true');
consoleSpy.mockRestore();
});
});
describe('Monitor integration', () => {
test('should call monitor.log when available', () => {
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
Logger.info('test message');
expect(monitor.log).toHaveBeenCalledWith(
expect.stringContaining('test message')
);
consoleSpy.mockRestore();
});
test('should handle missing monitor gracefully', () => {
// Temporarily remove monitor
const originalMonitor = monitor.log;
monitor.log = undefined;
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
expect(() => {
Logger.info('test message');
}).not.toThrow();
monitor.log = originalMonitor;
consoleSpy.mockRestore();
});
});
describe('Error handling', () => {
test('should handle console method not available', () => {
const originalConsole = console.debug;
console.debug = undefined;
expect(() => {
Logger._write('debug', '[DEBUG]', ['test']);
}).not.toThrow();
console.debug = originalConsole;
});
test('should handle JSON.stringify errors', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
// Create circular reference
const circular = {};
circular.self = circular;
expect(() => {
Logger.error('Circular:', circular);
}).not.toThrow();
consoleSpy.mockRestore();
});
});
describe('Performance', () => {
test('should be performant with many log calls', () => {
Logger.enabled = false; // Disable actual logging for performance test
const start = performance.now();
for (let i = 0; i < 1000; i++) {
Logger.info(`Log message ${i}`);
}
const end = performance.now();
const duration = end - start;
// Should complete 1000 calls in under 100ms when disabled
expect(duration).toBeLessThan(100);
});
});
});

View File

@@ -0,0 +1,279 @@
/**
* @jest-environment jsdom
*/
import { StateManager } from '../../resources/js/core/StateManager.js';
// Mock Logger
jest.mock('../../resources/js/core/logger.js', () => ({
Logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
}
}));
describe('StateManager', () => {
let stateManager;
beforeEach(() => {
stateManager = new StateManager();
stateManager.setContext('test-module');
});
describe('State registration', () => {
test('should register state with default value', () => {
stateManager.register('testKey', 'defaultValue');
expect(stateManager.get('testKey')).toBe('defaultValue');
});
test('should not allow duplicate registration', () => {
stateManager.register('testKey', 'value1');
stateManager.register('testKey', 'value2');
expect(stateManager.get('testKey')).toBe('value1');
});
test('should track state ownership', () => {
stateManager.setContext('module-a');
stateManager.register('keyA', 'valueA');
stateManager.setContext('module-b');
stateManager.register('keyB', 'valueB');
expect(stateManager.stateOwners.get('keyA')).toBe('module-a');
expect(stateManager.stateOwners.get('keyB')).toBe('module-b');
});
});
describe('State access', () => {
beforeEach(() => {
stateManager.register('testKey', 42);
});
test('should get current state value', () => {
expect(stateManager.get('testKey')).toBe(42);
});
test('should return undefined for unknown keys', () => {
expect(stateManager.get('unknownKey')).toBeUndefined();
});
test('should allow owner to set value', () => {
const success = stateManager.set('testKey', 100);
expect(success).toBe(true);
expect(stateManager.get('testKey')).toBe(100);
});
test('should prevent non-owner from setting value', () => {
stateManager.setContext('other-module');
const success = stateManager.set('testKey', 200);
expect(success).toBe(false);
expect(stateManager.get('testKey')).toBe(42);
});
test('should allow forced set by non-owner', () => {
stateManager.setContext('other-module');
const success = stateManager.set('testKey', 300, true);
expect(success).toBe(true);
expect(stateManager.get('testKey')).toBe(300);
});
test('should not trigger subscribers for same value', () => {
const callback = jest.fn();
stateManager.subscribe('testKey', callback);
stateManager.set('testKey', 42); // Same value
expect(callback).not.toHaveBeenCalled();
});
});
describe('State subscriptions', () => {
beforeEach(() => {
stateManager.register('testKey', 'initial');
});
test('should subscribe to state changes', () => {
const callback = jest.fn();
const subscriptionId = stateManager.subscribe('testKey', callback);
expect(subscriptionId).toBeTruthy();
expect(typeof subscriptionId).toBe('string');
});
test('should notify subscribers on state change', () => {
const callback = jest.fn();
stateManager.subscribe('testKey', callback);
stateManager.set('testKey', 'changed');
expect(callback).toHaveBeenCalledWith('changed', 'initial', 'testKey');
});
test('should handle multiple subscribers', () => {
const callback1 = jest.fn();
const callback2 = jest.fn();
stateManager.subscribe('testKey', callback1);
stateManager.subscribe('testKey', callback2);
stateManager.set('testKey', 'updated');
expect(callback1).toHaveBeenCalledWith('updated', 'initial', 'testKey');
expect(callback2).toHaveBeenCalledWith('updated', 'initial', 'testKey');
});
test('should unsubscribe correctly', () => {
const callback = jest.fn();
const subscriptionId = stateManager.subscribe('testKey', callback);
stateManager.unsubscribe(subscriptionId);
stateManager.set('testKey', 'after-unsubscribe');
expect(callback).not.toHaveBeenCalled();
});
test('should handle subscriber errors gracefully', () => {
const errorCallback = jest.fn(() => {
throw new Error('Subscriber error');
});
const goodCallback = jest.fn();
stateManager.subscribe('testKey', errorCallback);
stateManager.subscribe('testKey', goodCallback);
expect(() => {
stateManager.set('testKey', 'trigger-error');
}).not.toThrow();
expect(goodCallback).toHaveBeenCalled();
});
test('should not subscribe to unknown keys', () => {
const callback = jest.fn();
const subscriptionId = stateManager.subscribe('unknownKey', callback);
expect(subscriptionId).toBeNull();
});
});
describe('State reset', () => {
beforeEach(() => {
stateManager.register('testKey', 'default');
stateManager.set('testKey', 'changed');
});
test('should reset to default value', () => {
const success = stateManager.reset('testKey');
expect(success).toBe(true);
expect(stateManager.get('testKey')).toBe('default');
});
test('should notify subscribers on reset', () => {
const callback = jest.fn();
stateManager.subscribe('testKey', callback);
stateManager.reset('testKey');
expect(callback).toHaveBeenCalledWith('default', 'changed', 'testKey');
});
test('should not reset unknown keys', () => {
const success = stateManager.reset('unknownKey');
expect(success).toBe(false);
});
});
describe('Module scoped interface', () => {
test('should create scoped state manager', () => {
const scoped = stateManager.createScope('scoped-module');
expect(scoped).toHaveProperty('register');
expect(scoped).toHaveProperty('get');
expect(scoped).toHaveProperty('set');
expect(scoped).toHaveProperty('subscribe');
expect(scoped).toHaveProperty('cleanup');
});
test('should use module context in scoped operations', () => {
const scoped = stateManager.createScope('scoped-module');
scoped.register('scopedKey', 'scopedValue');
expect(stateManager.stateOwners.get('scopedKey')).toBe('scoped-module');
});
test('should cleanup module subscriptions', () => {
const scoped = stateManager.createScope('cleanup-module');
const callback = jest.fn();
scoped.register('cleanupKey', 'value');
const subscriptionId = scoped.subscribe('cleanupKey', callback);
scoped.cleanup();
// Subscription should be cleaned up
stateManager.set('cleanupKey', 'after-cleanup', true);
expect(callback).not.toHaveBeenCalled();
});
});
describe('Debugging and introspection', () => {
test('should provide state snapshot', () => {
stateManager.register('key1', 'value1');
stateManager.register('key2', 42);
const snapshot = stateManager.getSnapshot();
expect(snapshot).toHaveProperty('state');
expect(snapshot).toHaveProperty('owners');
expect(snapshot).toHaveProperty('subscriptions');
expect(snapshot.state.key1).toBe('value1');
expect(snapshot.state.key2).toBe(42);
});
test('should reset entire state manager', () => {
stateManager.register('key1', 'value1');
stateManager.subscribe('key1', jest.fn());
stateManager.resetAll();
expect(stateManager.state.size).toBe(0);
expect(stateManager.subscribers.size).toBe(0);
expect(stateManager.stateOwners.size).toBe(0);
});
});
describe('Performance', () => {
test('should handle many state operations efficiently', () => {
const start = performance.now();
// Register many state keys
for (let i = 0; i < 100; i++) {
stateManager.register(`key${i}`, i);
}
// Subscribe to all keys
for (let i = 0; i < 100; i++) {
stateManager.subscribe(`key${i}`, jest.fn());
}
// Update all keys
for (let i = 0; i < 100; i++) {
stateManager.set(`key${i}`, i * 2);
}
const end = performance.now();
const duration = end - start;
// Should complete in reasonable time
expect(duration).toBeLessThan(100);
});
});
});