// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= import { vi } from 'vitest'; export interface MockedElectronAPI { // Mock environment state that can be controlled in tests mockState: { venvExists: boolean; versionFileExists: boolean; currentVersion: string; savedVersion: string; isInstalling: boolean; installedLockExists: boolean; uvicornStarting: boolean; toolInstalled: boolean; allowForceInstall: boolean; // Environment-related state envFileExists: boolean; envContent: string; eigentDirExists: boolean; userEmail: string; mcpRemoteConfigExists: boolean; hasToken: boolean; }; // Mock implementation functions checkAndInstallDepsOnUpdate: ReturnType; getInstallationStatus: ReturnType; exportLog: ReturnType; onInstallDependenciesStart: ReturnType; onInstallDependenciesLog: ReturnType; onInstallDependenciesComplete: ReturnType; removeAllListeners: ReturnType; // EnvUtil mock functions getEnvPath: ReturnType; updateEnvBlock: ReturnType; removeEnvKey: ReturnType; getEmailFolderPath: ReturnType; parseEnvBlock: ReturnType; // Test utilities simulateInstallationStart: () => void; simulateInstallationLog: (type: 'stdout' | 'stderr', data: string) => void; simulateInstallationComplete: (success: boolean, error?: string) => void; simulateVersionChange: (newVersion: string) => void; simulateVenvRemoval: () => void; simulateUvicornStartup: () => void; simulateEnvCorruption: () => void; simulateUserEmailChange: (email: string) => void; simulateMcpConfigMissing: () => void; reset: () => void; } export interface MockedIpcRenderer { invoke: ReturnType; on: ReturnType; removeAllListeners: ReturnType; } /** * Creates a comprehensive mock for the Electron API * This mock can simulate all the different installation scenarios */ export function createElectronAPIMock(): MockedElectronAPI { // Listeners for simulation const installStartListeners: Array<() => void> = []; const installLogListeners: Array< (data: { type: string; data: string }) => void > = []; const installCompleteListeners: Array< (data: { success: boolean; code?: number; error?: string }) => void > = []; const mockState = { venvExists: true, versionFileExists: true, currentVersion: '1.0.0', savedVersion: '1.0.0', isInstalling: false, installedLockExists: true, uvicornStarting: false, toolInstalled: true, allowForceInstall: false, // Environment-related state envFileExists: true, envContent: 'MOCK_VAR=mock_value\n# === MCP INTEGRATION ENV START ===\nMCP_KEY=test_value\n# === MCP INTEGRATION ENV END ===', eigentDirExists: true, userEmail: 'test@example.com', mcpRemoteConfigExists: true, hasToken: true, }; const electronAPI: MockedElectronAPI = { mockState, // Core API functions checkAndInstallDepsOnUpdate: vi.fn().mockImplementation(async () => { const { versionFileExists, currentVersion, savedVersion, allowForceInstall, venvExists, toolInstalled, } = mockState; // Simulate the real implementation logic that checks: // 1. Version file existence and version match // 2. Virtual environment existence // 3. Command tools installation status const versionChanged = !versionFileExists || savedVersion !== currentVersion; const needsInstallation = allowForceInstall || versionChanged || !venvExists || !toolInstalled; if (needsInstallation) { // Log the reason for installation if (!toolInstalled) { electronAPI.simulateInstallationLog( 'stdout', 'Command tools missing, starting installation...' ); } else if (!venvExists) { electronAPI.simulateInstallationLog( 'stdout', 'Virtual environment missing, starting installation...' ); } else if (versionChanged) { electronAPI.simulateInstallationLog( 'stdout', 'Version changed, starting installation...' ); } // Trigger installation electronAPI.simulateInstallationStart(); // Simulate installation process with delay setTimeout(() => { electronAPI.simulateInstallationLog( 'stdout', 'Resolving dependencies...' ); setTimeout(() => { electronAPI.simulateInstallationLog( 'stdout', 'Installing packages...' ); setTimeout(() => { electronAPI.simulateInstallationComplete(true); // Update state after successful installation mockState.venvExists = true; mockState.toolInstalled = true; mockState.installedLockExists = true; }, 100); }, 100); }, 50); return { success: true, message: 'Dependencies installed successfully after update', }; } else { return { success: true, message: 'Version not changed, venv exists, and tools installed - skipped installation', }; } }), getInstallationStatus: vi.fn().mockImplementation(async () => { return { success: true, isInstalling: mockState.isInstalling, hasLockFile: mockState.isInstalling || mockState.installedLockExists, installedExists: mockState.installedLockExists, }; }), exportLog: vi.fn().mockImplementation(async () => { return { success: true, savedPath: '/mock/path/to/log.txt', }; }), // Event listeners onInstallDependenciesStart: vi .fn() .mockImplementation((callback: () => void) => { installStartListeners.push(callback); }), onInstallDependenciesLog: vi .fn() .mockImplementation( (callback: (data: { type: string; data: string }) => void) => { installLogListeners.push(callback); } ), onInstallDependenciesComplete: vi .fn() .mockImplementation( ( callback: (data: { success: boolean; code?: number; error?: string; }) => void ) => { installCompleteListeners.push(callback); } ), removeAllListeners: vi.fn().mockImplementation(() => { installStartListeners.length = 0; installLogListeners.length = 0; installCompleteListeners.length = 0; }), // EnvUtil mock functions getEnvPath: vi.fn().mockImplementation((email: string) => { const sanitizedEmail = email .split('@')[0] .replace(/[\\/*?:"<>|\s]/g, '_') .replace('.', '_'); return `/mock/home/.eigent/.env.${sanitizedEmail}`; }), updateEnvBlock: vi .fn() .mockImplementation((lines: string[], kv: Record) => { // Mock implementation that adds/updates environment variables in the MCP block const startMarker = '# === MCP INTEGRATION ENV START ==='; const endMarker = '# === MCP INTEGRATION ENV END ==='; let start = lines.findIndex((l) => l.trim() === startMarker); let end = lines.findIndex((l) => l.trim() === endMarker); if (start === -1 || end === -1) { // No block exists, create one lines.push(startMarker); Object.entries(kv).forEach(([k, v]) => { lines.push(`${k}=${v}`); }); lines.push(endMarker); return lines; } // Update existing block const newBlock = Object.entries(kv).map(([k, v]) => `${k}=${v}`); return [...lines.slice(0, start + 1), ...newBlock, ...lines.slice(end)]; }), removeEnvKey: vi.fn().mockImplementation((lines: string[], key: string) => { // Mock implementation that removes a key from the MCP block const startMarker = '# === MCP INTEGRATION ENV START ==='; const endMarker = '# === MCP INTEGRATION ENV END ==='; let start = lines.findIndex((l) => l.trim() === startMarker); let end = lines.findIndex((l) => l.trim() === endMarker); if (start === -1 || end === -1) return lines; const block = lines.slice(start + 1, end); const newBlock = block.filter((line) => !line.startsWith(key + '=')); return [...lines.slice(0, start + 1), ...newBlock, ...lines.slice(end)]; }), getEmailFolderPath: vi.fn().mockImplementation((email: string) => { const sanitizedEmail = email .split('@')[0] .replace(/[\\/*?:"<>|\s]/g, '_') .replace('.', '_'); return { MCP_REMOTE_CONFIG_DIR: `/mock/home/.eigent/${sanitizedEmail}`, MCP_CONFIG_DIR: '/mock/home/.eigent', tempEmail: sanitizedEmail, hasToken: mockState.hasToken, }; }), parseEnvBlock: vi.fn().mockImplementation((content: string) => { const lines = content.split(/\r?\n/); const startMarker = '# === MCP INTEGRATION ENV START ==='; const endMarker = '# === MCP INTEGRATION ENV END ==='; let start = lines.findIndex((l) => l.trim() === startMarker); let end = lines.findIndex((l) => l.trim() === endMarker); if (start === -1) start = lines.length; if (end === -1) end = lines.length; return { lines, start, end }; }), // Simulation utilities simulateInstallationStart: () => { mockState.isInstalling = true; installStartListeners.forEach((listener) => listener()); }, simulateInstallationLog: (type: 'stdout' | 'stderr', data: string) => { installLogListeners.forEach((listener) => listener({ type, data })); }, simulateInstallationComplete: (success: boolean, error?: string) => { mockState.isInstalling = false; if (success) { mockState.installedLockExists = true; } installCompleteListeners.forEach((listener) => listener({ success, error, code: success ? 0 : 1 }) ); }, simulateVersionChange: (newVersion: string) => { mockState.currentVersion = newVersion; // This simulates a version mismatch scenario }, simulateVenvRemoval: () => { mockState.venvExists = false; mockState.installedLockExists = false; // Don't remove version file - this simulates venv being deleted but version file still existing }, simulateUvicornStartup: () => { mockState.uvicornStarting = true; // Simulate uvicorn detecting dependency installation need setTimeout(() => { electronAPI.simulateInstallationStart(); electronAPI.simulateInstallationLog( 'stdout', 'Uvicorn detected missing dependencies' ); electronAPI.simulateInstallationLog( 'stdout', 'Resolving dependencies...' ); setTimeout(() => { electronAPI.simulateInstallationLog( 'stdout', 'Uvicorn running on http://127.0.0.1:8000' ); electronAPI.simulateInstallationComplete(true); mockState.uvicornStarting = false; }, 200); }, 100); }, simulateEnvCorruption: () => { mockState.envFileExists = true; mockState.envContent = 'INVALID_ENV_CONTENT\n# === MCP INTEGRATION ENV START ===\nBROKEN'; }, simulateUserEmailChange: (email: string) => { mockState.userEmail = email; }, simulateMcpConfigMissing: () => { mockState.mcpRemoteConfigExists = false; }, reset: () => { Object.assign(mockState, { venvExists: true, versionFileExists: true, currentVersion: '1.0.0', savedVersion: '1.0.0', isInstalling: false, installedLockExists: true, uvicornStarting: false, toolInstalled: true, allowForceInstall: false, // Reset environment-related state envFileExists: true, envContent: 'MOCK_VAR=mock_value\n# === MCP INTEGRATION ENV START ===\nMCP_KEY=test_value\n# === MCP INTEGRATION ENV END ===', eigentDirExists: true, userEmail: 'test@example.com', mcpRemoteConfigExists: true, hasToken: true, }); // Clear all listeners installStartListeners.length = 0; installLogListeners.length = 0; installCompleteListeners.length = 0; // Reset all mocks electronAPI.checkAndInstallDepsOnUpdate.mockClear(); electronAPI.getInstallationStatus.mockClear(); electronAPI.exportLog.mockClear(); electronAPI.onInstallDependenciesStart.mockClear(); electronAPI.onInstallDependenciesLog.mockClear(); electronAPI.onInstallDependenciesComplete.mockClear(); electronAPI.removeAllListeners.mockClear(); electronAPI.getEnvPath.mockClear(); electronAPI.updateEnvBlock.mockClear(); electronAPI.removeEnvKey.mockClear(); electronAPI.getEmailFolderPath.mockClear(); electronAPI.parseEnvBlock.mockClear(); }, }; return electronAPI; } /** * Creates a mock for the IPC Renderer */ export function createIpcRendererMock(): MockedIpcRenderer { return { invoke: vi .fn() .mockImplementation(async (channel: string, ..._args: any[]) => { if (channel === 'check-tool-installed') { return { success: true, isInstalled: true, // This can be controlled via the electronAPI mock }; } return { success: false, error: 'Unknown channel' }; }), on: vi.fn(), removeAllListeners: vi.fn(), }; } /** * Test utility to set up all Electron mocks */ export function setupElectronMocks() { const electronAPI = createElectronAPIMock(); const ipcRenderer = createIpcRendererMock(); // Set up global mocks Object.defineProperty(window, 'electronAPI', { value: electronAPI, writable: true, }); Object.defineProperty(window, 'ipcRenderer', { value: ipcRenderer, writable: true, }); return { electronAPI, ipcRenderer }; } /** * Predefined test scenarios */ export const TestScenarios = { /** * Fresh installation - no venv, no version file */ freshInstall: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.venvExists = false; electronAPI.mockState.versionFileExists = false; electronAPI.mockState.installedLockExists = false; electronAPI.mockState.toolInstalled = false; }, /** * Version update scenario - version file exists but version changed */ versionUpdate: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.versionFileExists = true; electronAPI.mockState.savedVersion = '0.9.0'; electronAPI.mockState.currentVersion = '1.0.0'; electronAPI.mockState.installedLockExists = false; }, /** * Venv removed scenario - version file exists but .venv is missing */ venvRemoved: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.venvExists = false; electronAPI.mockState.versionFileExists = true; electronAPI.mockState.installedLockExists = false; }, /** * Installation in progress - when user opens app during installation */ installationInProgress: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.isInstalling = true; electronAPI.mockState.installedLockExists = false; }, /** * Installation error scenario */ installationError: (electronAPI: MockedElectronAPI) => { electronAPI.checkAndInstallDepsOnUpdate.mockImplementation(async () => { electronAPI.simulateInstallationStart(); setTimeout(() => { electronAPI.simulateInstallationLog( 'stderr', 'Error: Failed to resolve dependencies' ); electronAPI.simulateInstallationComplete(false, 'Installation failed'); }, 100); return { success: false, message: 'Installation failed' }; }); }, /** * Uvicorn startup with dependency installation */ uvicornDepsInstall: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.uvicornStarting = true; electronAPI.mockState.isInstalling = false; // The simulateUvicornStartup method will handle the rest }, /** * All good - no installation needed */ allGood: (electronAPI: MockedElectronAPI) => { electronAPI.mockState.venvExists = true; electronAPI.mockState.versionFileExists = true; electronAPI.mockState.savedVersion = electronAPI.mockState.currentVersion; electronAPI.mockState.installedLockExists = true; electronAPI.mockState.isInstalling = false; electronAPI.mockState.toolInstalled = true; }, };