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('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' }), ], }), ); }); });