diff --git a/src/relay/pty-shell-utils.test.ts b/src/relay/pty-shell-utils.test.ts index d4f69f4dd4b..ac913e689f0 100644 --- a/src/relay/pty-shell-utils.test.ts +++ b/src/relay/pty-shell-utils.test.ts @@ -1,7 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { execFileMock, execFileSyncMock } = vi.hoisted(() => ({ +const { execFileMock, execFileSyncMock, getAllProcessesMock } = vi.hoisted(() => ({ execFileMock: vi.fn(), + getAllProcessesMock: vi.fn(), execFileSyncMock: vi.fn() })) @@ -11,6 +12,7 @@ vi.mock('child_process', () => ({ })) import { resetWindowsProcessRowsSnapshotForTests } from '../main/providers/windows-foreground-process-rows' +import { __setWindowsProcessTreeLoaderForTests } from '../main/windows/windows-process-table' import { resetProcessTableSnapshotForTests } from '../shared/process-table-snapshot' import { getForegroundProcessName, @@ -35,6 +37,21 @@ function mockExecFile( ) } +/** + * Feed the native Windows snapshot. A real snapshot always contains the + * querying process, and the reader rejects a table without it. + */ +function mockWindowsProcessTable( + rows: { pid: number; ppid: number; name: string; commandLine?: string }[] +): void { + __setWindowsProcessTreeLoaderForTests(() => ({ + ProcessDataFlag: { None: 0, Memory: 1, CommandLine: 2 }, + getAllProcesses: (cb: (value: typeof rows | undefined) => void) => + cb([{ pid: process.pid, ppid: 0, name: 'vitest.exe', commandLine: 'vitest' }, ...rows]) + })) + getAllProcessesMock.mockClear() +} + async function withProcessPlatform( platform: NodeJS.Platform, run: () => T | Promise @@ -56,6 +73,7 @@ beforeEach(() => { execFileSyncMock.mockReset() resetProcessTableSnapshotForTests() resetWindowsProcessRowsSnapshotForTests() + __setWindowsProcessTreeLoaderForTests() }) describe('isProcessAlive', () => { @@ -306,29 +324,15 @@ describe('getForegroundProcessName', () => { it('recognizes Windows SSH relay shell-rooted agent descendants', async () => { await withProcessPlatform('win32', async () => { - mockExecFile((command) => { - if (command === 'powershell.exe') { - return { - stdout: JSON.stringify([ - { - CommandLine: 'powershell.exe', - ExecutablePath: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', - Name: 'powershell.exe', - ParentProcessId: 99, - ProcessId: 100 - }, - { - CommandLine: 'node C:\\Users\\dev\\AppData\\Roaming\\npm\\codex.cmd', - ExecutablePath: 'C:\\Program Files\\nodejs\\node.exe', - Name: 'node.exe', - ParentProcessId: 100, - ProcessId: 101 - } - ]) - } + mockWindowsProcessTable([ + { pid: 100, ppid: 99, name: 'powershell.exe', commandLine: 'powershell.exe' }, + { + pid: 101, + ppid: 100, + name: 'node.exe', + commandLine: 'node C:\\Users\\dev\\AppData\\Roaming\\npm\\codex.cmd' } - return new Error('unexpected command') - }) + ]) await expect(getForegroundProcessName(100, 'powershell.exe')).resolves.toBe('codex') }) @@ -430,36 +434,11 @@ describe('getForegroundProcessName', () => { it('rescans a Windows pi fallback for its outer omp wrapper', async () => { await withProcessPlatform('win32', async () => { - mockExecFile((command) => { - if (command === 'powershell.exe') { - return { - stdout: JSON.stringify([ - { - CommandLine: 'powershell.exe', - ExecutablePath: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', - Name: 'powershell.exe', - ParentProcessId: 99, - ProcessId: 100 - }, - { - CommandLine: 'omp.exe', - ExecutablePath: 'C:\\Tools\\omp.exe', - Name: 'omp.exe', - ParentProcessId: 100, - ProcessId: 101 - }, - { - CommandLine: 'pi.exe', - ExecutablePath: 'C:\\Tools\\pi.exe', - Name: 'pi.exe', - ParentProcessId: 101, - ProcessId: 102 - } - ]) - } - } - return new Error('unexpected command') - }) + mockWindowsProcessTable([ + { pid: 100, ppid: 99, name: 'powershell.exe', commandLine: 'powershell.exe' }, + { pid: 101, ppid: 100, name: 'omp.exe', commandLine: 'omp.exe' }, + { pid: 102, ppid: 101, name: 'pi.exe', commandLine: 'pi.exe' } + ]) await expect(getForegroundProcessName(100, 'pi')).resolves.toBe('omp') })