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,387 @@
/**
* @jest-environment jsdom
*/
// Mock all dependencies
jest.mock('../../resources/js/core/logger.js', () => ({
Logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
}
}));
jest.mock('../../resources/js/core/ModuleErrorBoundary.js', () => ({
moduleErrorBoundary: {
wrapModule: jest.fn((mod, name) => mod),
reset: jest.fn()
}
}));
jest.mock('../../resources/js/core/StateManager.js', () => ({
stateManager: {
createScope: jest.fn(() => {
const state = new Map();
const subscribers = new Map();
return {
register: jest.fn((key, defaultValue) => {
state.set(key, defaultValue);
subscribers.set(key, []);
}),
get: jest.fn((key) => state.get(key)),
set: jest.fn((key, value) => {
const oldValue = state.get(key);
state.set(key, value);
const subs = subscribers.get(key) || [];
subs.forEach(callback => callback(value, oldValue, key));
return true;
}),
subscribe: jest.fn((key, callback) => {
if (!subscribers.has(key)) {
subscribers.set(key, []);
}
subscribers.get(key).push(callback);
return `sub_${key}_${Date.now()}`;
}),
unsubscribe: jest.fn(),
cleanup: jest.fn(),
reset: jest.fn(() => {
state.clear();
subscribers.clear();
})
};
}),
resetAll: jest.fn()
}
}));
jest.mock('../../resources/js/core/DependencyManager.js', () => ({
dependencyManager: {
register: jest.fn(),
calculateInitializationOrder: jest.fn(() => ['test-module']),
checkDependencies: jest.fn(() => ({ satisfied: true, missing: [] })),
markInitializing: jest.fn(),
markInitialized: jest.fn(),
reset: jest.fn()
}
}));
// Use manual mock for modules/index.js
jest.mock('../../resources/js/modules/index.js');
// Mock import.meta.glob first
const mockModules = {
'./test-module/index.js': {
init: jest.fn(),
destroy: jest.fn(),
definition: {
name: 'test-module',
version: '1.0.0',
dependencies: [],
provides: [],
priority: 0
}
}
};
// Create mock import.meta for Babel environment
global.importMeta = {
glob: jest.fn(() => mockModules)
};
import { registerModules, destroyModules, getModuleHealth, activeModules } from '../../resources/js/modules/index.js';
import { moduleErrorBoundary } from '../../resources/js/core/ModuleErrorBoundary.js';
import { stateManager } from '../../resources/js/core/StateManager.js';
import { dependencyManager } from '../../resources/js/core/DependencyManager.js';
describe('Module System', () => {
beforeEach(() => {
// Clear active modules
activeModules.clear();
// Reset all mocks
jest.clearAllMocks();
// Reset DOM
document.body.innerHTML = '';
});
describe('Module registration', () => {
test('should register modules without DOM selectors in fallback mode', async () => {
await registerModules();
expect(dependencyManager.register).toHaveBeenCalledWith(
expect.objectContaining({
name: 'test-module',
version: '1.0.0'
})
);
expect(dependencyManager.calculateInitializationOrder).toHaveBeenCalled();
expect(mockModules['./test-module/index.js'].init).toHaveBeenCalled();
expect(activeModules.has('test-module')).toBe(true);
});
test('should respect DOM-based module selection', async () => {
// Add module selector to DOM
const element = document.createElement('div');
element.setAttribute('data-module', 'test-module');
document.body.appendChild(element);
await registerModules();
expect(mockModules['./test-module/index.js'].init).toHaveBeenCalled();
});
test('should skip modules not used in DOM when not in fallback mode', async () => {
// Add different module selector to DOM
const element = document.createElement('div');
element.setAttribute('data-module', 'other-module');
document.body.appendChild(element);
await registerModules();
expect(mockModules['./test-module/index.js'].init).not.toHaveBeenCalled();
});
test('should create default definition for modules without explicit definition', async () => {
// Module without definition
const moduleWithoutDef = {
init: jest.fn(),
destroy: jest.fn()
};
global.importMeta.glob.mockReturnValue({
'./no-def-module/index.js': moduleWithoutDef
});
dependencyManager.calculateInitializationOrder.mockReturnValue(['no-def-module']);
await registerModules();
expect(dependencyManager.register).toHaveBeenCalledWith(
expect.objectContaining({
name: 'no-def-module',
version: '1.0.0',
dependencies: [],
provides: [],
priority: 0
})
);
});
test('should handle module initialization errors', async () => {
const errorModule = {
init: jest.fn(() => {
throw new Error('Init failed');
}),
destroy: jest.fn()
};
global.importMeta.glob.mockReturnValue({
'./error-module/index.js': errorModule
});
dependencyManager.calculateInitializationOrder.mockReturnValue(['error-module']);
await registerModules();
const moduleData = activeModules.get('error-module');
expect(moduleData.mod).toBeNull();
expect(moduleData.error).toBeInstanceOf(Error);
});
test('should handle unsatisfied dependencies', async () => {
dependencyManager.checkDependencies.mockReturnValue({
satisfied: false,
missing: ['missing-dependency'],
reason: 'Missing: missing-dependency'
});
await registerModules();
const moduleData = activeModules.get('test-module');
expect(moduleData.mod).toBeNull();
expect(moduleData.error.message).toBe('Missing: missing-dependency');
});
test('should pass config and state to module init', async () => {
// Mock module config
jest.doMock('../../resources/js/modules/config.js', () => ({
moduleConfig: {
'test-module': { setting: 'value' }
}
}), { virtual: true });
await registerModules();
expect(mockModules['./test-module/index.js'].init).toHaveBeenCalledWith(
{ setting: 'value' },
expect.any(Object) // scoped state manager
);
});
test('should wrap modules with error boundary', async () => {
await registerModules();
expect(moduleErrorBoundary.wrapModule).toHaveBeenCalledWith(
mockModules['./test-module/index.js'],
'test-module'
);
});
test('should create scoped state manager for each module', async () => {
await registerModules();
expect(stateManager.createScope).toHaveBeenCalledWith('test-module');
});
test('should track initialization with dependency manager', async () => {
await registerModules();
expect(dependencyManager.markInitializing).toHaveBeenCalledWith('test-module');
expect(dependencyManager.markInitialized).toHaveBeenCalledWith('test-module');
});
});
describe('Module destruction', () => {
beforeEach(async () => {
await registerModules();
});
test('should call destroy on all modules', () => {
destroyModules();
expect(mockModules['./test-module/index.js'].destroy).toHaveBeenCalled();
});
test('should cleanup state subscriptions', () => {
const mockState = {
cleanup: jest.fn()
};
// Manually add module with state
activeModules.set('test-module', {
mod: mockModules['./test-module/index.js'],
config: {},
state: mockState
});
destroyModules();
expect(mockState.cleanup).toHaveBeenCalled();
});
test('should handle destroy errors gracefully', () => {
const errorModule = {
destroy: jest.fn(() => {
throw new Error('Destroy failed');
})
};
activeModules.set('error-module', {
mod: errorModule,
config: {},
state: null
});
expect(() => destroyModules()).not.toThrow();
});
test('should reset all managers', () => {
destroyModules();
expect(moduleErrorBoundary.reset).toHaveBeenCalled();
expect(stateManager.reset).toHaveBeenCalled();
expect(dependencyManager.reset).toHaveBeenCalled();
});
test('should clear active modules map', () => {
destroyModules();
expect(activeModules.size).toBe(0);
});
});
describe('Module health monitoring', () => {
test('should report health status correctly', async () => {
await registerModules();
const health = getModuleHealth();
expect(health.total).toBe(1);
expect(health.active).toBe(1);
expect(health.failed).toBe(0);
expect(health.modules['test-module']).toEqual({ status: 'active' });
});
test('should report failed modules', () => {
activeModules.set('failed-module', {
mod: null,
config: {},
error: new Error('Failed to init'),
original: mockModules['./test-module/index.js']
});
const health = getModuleHealth();
expect(health.failed).toBe(1);
expect(health.modules['failed-module']).toEqual({
status: 'failed',
error: 'Failed to init'
});
});
test('should include error boundary status', async () => {
moduleErrorBoundary.getHealthStatus = jest.fn(() => ({
totalCrashedModules: 0,
crashedModules: [],
recoveryAttempts: {}
}));
await registerModules();
const health = getModuleHealth();
expect(health.errorBoundary).toBeDefined();
expect(health.errorBoundary.totalCrashedModules).toBe(0);
});
});
describe('Integration tests', () => {
test('should handle complete module lifecycle', async () => {
// Register
await registerModules();
expect(activeModules.size).toBe(1);
expect(mockModules['./test-module/index.js'].init).toHaveBeenCalled();
// Destroy
destroyModules();
expect(mockModules['./test-module/index.js'].destroy).toHaveBeenCalled();
expect(activeModules.size).toBe(0);
});
test('should handle async module initialization', async () => {
const asyncModule = {
init: jest.fn(async () => {
await new Promise(resolve => setTimeout(resolve, 10));
}),
destroy: jest.fn()
};
global.importMeta.glob.mockReturnValue({
'./async-module/index.js': asyncModule
});
dependencyManager.calculateInitializationOrder.mockReturnValue(['async-module']);
await registerModules();
expect(asyncModule.init).toHaveBeenCalled();
expect(dependencyManager.markInitialized).toHaveBeenCalledWith('async-module');
});
});
});