fix(windows): Gateway process install extension failed (#587)
Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
188
tests/unit/plugin-install.test.ts
Normal file
188
tests/unit/plugin-install.test.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const {
|
||||
mockExistsSync,
|
||||
mockCpSync,
|
||||
mockMkdirSync,
|
||||
mockRmSync,
|
||||
mockReadFileSync,
|
||||
mockWriteFileSync,
|
||||
mockReaddirSync,
|
||||
mockRealpathSync,
|
||||
mockLoggerWarn,
|
||||
mockLoggerInfo,
|
||||
mockHomedir,
|
||||
mockApp,
|
||||
} = vi.hoisted(() => ({
|
||||
mockExistsSync: vi.fn(),
|
||||
mockCpSync: vi.fn(),
|
||||
mockMkdirSync: vi.fn(),
|
||||
mockRmSync: vi.fn(),
|
||||
mockReadFileSync: vi.fn(),
|
||||
mockWriteFileSync: vi.fn(),
|
||||
mockReaddirSync: vi.fn(),
|
||||
mockRealpathSync: vi.fn(),
|
||||
mockLoggerWarn: vi.fn(),
|
||||
mockLoggerInfo: vi.fn(),
|
||||
mockHomedir: vi.fn(() => '/home/test'),
|
||||
mockApp: {
|
||||
isPackaged: true,
|
||||
getAppPath: vi.fn(() => '/mock/app'),
|
||||
},
|
||||
}));
|
||||
|
||||
const ORIGINAL_PLATFORM_DESCRIPTOR = Object.getOwnPropertyDescriptor(process, 'platform');
|
||||
|
||||
vi.mock('node:fs', async () => {
|
||||
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
const mocked = {
|
||||
...actual,
|
||||
existsSync: mockExistsSync,
|
||||
cpSync: mockCpSync,
|
||||
mkdirSync: mockMkdirSync,
|
||||
rmSync: mockRmSync,
|
||||
readFileSync: mockReadFileSync,
|
||||
writeFileSync: mockWriteFileSync,
|
||||
readdirSync: mockReaddirSync,
|
||||
realpathSync: mockRealpathSync,
|
||||
};
|
||||
return {
|
||||
...mocked,
|
||||
default: mocked,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('node:os', () => ({
|
||||
homedir: () => mockHomedir(),
|
||||
default: {
|
||||
homedir: () => mockHomedir(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: mockApp,
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/logger', () => ({
|
||||
logger: {
|
||||
warn: mockLoggerWarn,
|
||||
info: mockLoggerInfo,
|
||||
},
|
||||
}));
|
||||
|
||||
function setPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: platform,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
describe('plugin installer diagnostics', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
mockApp.isPackaged = true;
|
||||
mockHomedir.mockReturnValue('/home/test');
|
||||
setPlatform('linux');
|
||||
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
mockCpSync.mockImplementation(() => undefined);
|
||||
mockMkdirSync.mockImplementation(() => undefined);
|
||||
mockRmSync.mockImplementation(() => undefined);
|
||||
mockReadFileSync.mockReturnValue('{}');
|
||||
mockWriteFileSync.mockImplementation(() => undefined);
|
||||
mockReaddirSync.mockReturnValue([]);
|
||||
mockRealpathSync.mockImplementation((input: string) => input);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (ORIGINAL_PLATFORM_DESCRIPTOR) {
|
||||
Object.defineProperty(process, 'platform', ORIGINAL_PLATFORM_DESCRIPTOR);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns source-missing warning when bundled mirror cannot be found', async () => {
|
||||
const { ensurePluginInstalled } = await import('@electron/utils/plugin-install');
|
||||
const result = ensurePluginInstalled('wecom', ['/bundle/wecom'], 'WeCom');
|
||||
|
||||
expect(result.installed).toBe(false);
|
||||
expect(result.warning).toContain('Bundled WeCom plugin mirror not found');
|
||||
expect(mockLoggerWarn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retries once on Windows and logs diagnostic details when bundled copy fails', async () => {
|
||||
setPlatform('win32');
|
||||
mockHomedir.mockReturnValue('C:\\Users\\test');
|
||||
|
||||
const sourceDir = 'C:\\Program Files\\ClawX\\resources\\openclaw-plugins\\wecom';
|
||||
const sourceManifestSuffix = 'Program Files\\ClawX\\resources\\openclaw-plugins\\wecom\\openclaw.plugin.json';
|
||||
|
||||
mockExistsSync.mockImplementation((input: string) => String(input).includes(sourceManifestSuffix));
|
||||
mockCpSync.mockImplementation(() => {
|
||||
const error = new Error('path too long') as NodeJS.ErrnoException;
|
||||
error.code = 'ENAMETOOLONG';
|
||||
throw error;
|
||||
});
|
||||
|
||||
const { ensurePluginInstalled } = await import('@electron/utils/plugin-install');
|
||||
const result = ensurePluginInstalled('wecom', [sourceDir], 'WeCom');
|
||||
|
||||
expect(result).toEqual({
|
||||
installed: false,
|
||||
warning: 'Failed to install bundled WeCom plugin mirror',
|
||||
});
|
||||
|
||||
expect(mockCpSync).toHaveBeenCalledTimes(2);
|
||||
const [firstSourcePath, firstTargetPath] = mockCpSync.mock.calls[0] as [string, string];
|
||||
expect(firstSourcePath.startsWith('\\\\?\\')).toBe(true);
|
||||
expect(firstTargetPath.startsWith('\\\\?\\')).toBe(true);
|
||||
|
||||
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
||||
'[plugin] Bundled mirror install failed for WeCom',
|
||||
expect.objectContaining({
|
||||
pluginDirName: 'wecom',
|
||||
pluginLabel: 'WeCom',
|
||||
sourceDir,
|
||||
platform: 'win32',
|
||||
attempts: [
|
||||
expect.objectContaining({ attempt: 1, code: 'ENAMETOOLONG' }),
|
||||
expect.objectContaining({ attempt: 2, code: 'ENAMETOOLONG' }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('logs EPERM diagnostics with source and target paths', async () => {
|
||||
setPlatform('win32');
|
||||
mockHomedir.mockReturnValue('C:\\Users\\test');
|
||||
|
||||
const sourceDir = 'C:\\Program Files\\ClawX\\resources\\openclaw-plugins\\wecom';
|
||||
const sourceManifestSuffix = 'Program Files\\ClawX\\resources\\openclaw-plugins\\wecom\\openclaw.plugin.json';
|
||||
|
||||
mockExistsSync.mockImplementation((input: string) => String(input).includes(sourceManifestSuffix));
|
||||
mockCpSync.mockImplementation(() => {
|
||||
const error = new Error('access denied') as NodeJS.ErrnoException;
|
||||
error.code = 'EPERM';
|
||||
throw error;
|
||||
});
|
||||
|
||||
const { ensurePluginInstalled } = await import('@electron/utils/plugin-install');
|
||||
const result = ensurePluginInstalled('wecom', [sourceDir], 'WeCom');
|
||||
|
||||
expect(result.installed).toBe(false);
|
||||
expect(result.warning).toBe('Failed to install bundled WeCom plugin mirror');
|
||||
|
||||
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
||||
'[plugin] Bundled mirror install failed for WeCom',
|
||||
expect.objectContaining({
|
||||
sourceDir,
|
||||
targetDir: expect.stringContaining('.openclaw/extensions/wecom'),
|
||||
platform: 'win32',
|
||||
attempts: [
|
||||
expect.objectContaining({ attempt: 1, code: 'EPERM' }),
|
||||
expect.objectContaining({ attempt: 2, code: 'EPERM' }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user