eigent/test/mocks/electronMocks.ts
Tong Chen 6c827a3d06
refactor: establish Brain-centered architecture and frontend/backend separation foundations (#1597)
Co-authored-by: Douglas <douglas.ym.lai@gmail.com>
Co-authored-by: Douglas Lai <115660088+Douglasymlai@users.noreply.github.com>
2026-05-01 17:03:33 +08:00

574 lines
18 KiB
TypeScript

// ========= 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<typeof vi.fn>;
getInstallationStatus: ReturnType<typeof vi.fn>;
exportLog: ReturnType<typeof vi.fn>;
getDiagnosticsInfo: ReturnType<typeof vi.fn>;
exportDiagnosticsZip: ReturnType<typeof vi.fn>;
openMailto: ReturnType<typeof vi.fn>;
onInstallDependenciesStart: ReturnType<typeof vi.fn>;
onInstallDependenciesLog: ReturnType<typeof vi.fn>;
onInstallDependenciesComplete: ReturnType<typeof vi.fn>;
removeAllListeners: ReturnType<typeof vi.fn>;
// EnvUtil mock functions
getEnvPath: ReturnType<typeof vi.fn>;
updateEnvBlock: ReturnType<typeof vi.fn>;
removeEnvKey: ReturnType<typeof vi.fn>;
getEmailFolderPath: ReturnType<typeof vi.fn>;
parseEnvBlock: ReturnType<typeof vi.fn>;
// 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<typeof vi.fn>;
on: ReturnType<typeof vi.fn>;
removeAllListeners: ReturnType<typeof vi.fn>;
}
/**
* 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',
};
}),
getDiagnosticsInfo: vi.fn().mockImplementation(async () => {
return {
version: '1.0.0',
platform: 'darwin',
arch: 'arm64',
};
}),
exportDiagnosticsZip: vi.fn().mockImplementation(async () => {
return {
success: true,
savedPath: '/mock/diagnostics.zip',
};
}),
openMailto: vi.fn().mockImplementation(async () => {
return { success: true };
}),
// 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<string, string>) => {
// 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.getDiagnosticsInfo.mockClear();
electronAPI.exportDiagnosticsZip.mockClear();
electronAPI.openMailto.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;
},
};