fix(windows): harden elevated runtime detection

This commit is contained in:
777genius 2026-05-22 18:43:50 +03:00
parent e9a37e7325
commit 077c749cb7
9 changed files with 735 additions and 30 deletions

View file

@ -28,8 +28,8 @@ import type {
} from '@features/runtime-provider-management/contracts';
import type { ChildProcessWithoutNullStreams } from 'child_process';
const COMMAND_TIMEOUT_MS = 45_000;
const PROBE_COMMAND_TIMEOUT_MS = 90_000;
const COMMAND_TIMEOUT_MS = PROBE_COMMAND_TIMEOUT_MS;
const COMMAND_ERROR_DETAIL_LIMIT = 1_600;
const COMMAND_OUTPUT_PREVIEW_LIMIT = 1_200;
const ESCAPE_CHARACTER = String.fromCharCode(27);

View file

@ -23,8 +23,8 @@ import type {
const logger = createLogger('ClaudeMultimodelBridgeService');
const PROVIDER_STATUS_TIMEOUT_MS = 25_000;
const PROVIDER_STATUS_SUMMARY_TIMEOUT_MS = 15_000;
const PROVIDER_STATUS_TIMEOUT_MS = 90_000;
const PROVIDER_STATUS_SUMMARY_TIMEOUT_MS = 30_000;
const PROVIDER_MODELS_TIMEOUT_MS = 25_000;
const PROVIDER_STATUS_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
const PROVIDER_MODELS_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
@ -995,8 +995,11 @@ export class ClaudeMultimodelBridgeService {
if (options.summary) {
args.push('--summary');
}
const timeout =
options.timeoutMs ??
(options.summary ? PROVIDER_STATUS_SUMMARY_TIMEOUT_MS : PROVIDER_STATUS_TIMEOUT_MS);
const { stdout } = await execCli(binaryPath, args, {
timeout: options.timeoutMs ?? PROVIDER_STATUS_TIMEOUT_MS,
timeout,
maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES,
env,
});

View file

@ -5,6 +5,24 @@ import type { WindowsElevationStatus } from '@shared/types/api';
const DEFAULT_WINDOWS_ELEVATION_TIMEOUT_MS = 3_000;
const DEFAULT_WINDOWS_SYSTEM_ROOT = 'C:\\Windows';
const NON_ELEVATED_FLTMC_PATTERNS = [
/\baccess\s+is\s+denied\b/i,
/\baccess\s+denied\b/i,
/\boperation\s+not\s+permitted\b/i,
/requires?\s+elevation/i,
/\belevated\b/i,
/\badministrator\b/i,
/\bprivileges?\b/i,
/\bpermission\s+denied\b/i,
/not\s+held\s+by\s+the\s+client/i,
];
const PROBE_UNAVAILABLE_PATTERNS = [
/cannot\s+find/i,
/not\s+found/i,
/not\s+recognized/i,
/not\s+a\s+valid\s+win32\s+application/i,
/bad\s+exe\s+format/i,
];
export interface WindowsElevationCommandResult {
error: unknown;
@ -22,6 +40,7 @@ export type WindowsElevationCommandRunner = (
export interface WindowsElevationStatusCheckerOptions {
platform?: string;
arch?: string;
systemRoot?: string;
timeoutMs?: number;
runCommand?: WindowsElevationCommandRunner;
@ -63,6 +82,10 @@ function wasKilledOrTimedOut(error: unknown): boolean {
return killed === true || signal === 'SIGTERM' || code === 'ETIMEDOUT';
}
function isMissingCommand(error: unknown): boolean {
return getErrorCode(error) === 'ENOENT';
}
function toCappedString(value: unknown): string | null {
if (typeof value === 'string') {
return value.slice(0, 500);
@ -84,8 +107,50 @@ function getErrorMessage(error: unknown, stderr: unknown): string | null {
return null;
}
function getFltmcPath(systemRoot: string): string {
return pathWin32.join(systemRoot, 'System32', 'fltmc.exe');
function getCombinedErrorText(error: unknown, stderr: unknown): string {
return [getErrorMessage(error, null), toCappedString(stderr)]
.filter((part): part is string => Boolean(part?.trim()))
.join('\n');
}
function looksLikeNonElevatedFltmcError(error: unknown, stderr: unknown): boolean {
const code = getErrorCode(error);
const combined = getCombinedErrorText(error, stderr);
if (PROBE_UNAVAILABLE_PATTERNS.some((pattern) => pattern.test(combined))) {
return false;
}
if (code === 1 || code === '1' || code === 5 || code === '5') {
return true;
}
return NON_ELEVATED_FLTMC_PATTERNS.some((pattern) => pattern.test(combined));
}
function normalizeWindowsRoot(value: string | null | undefined): string | null {
const normalized = value?.trim().replace(/^['"]|['"]$/g, '');
if (!normalized || !pathWin32.isAbsolute(normalized)) {
return null;
}
return normalized;
}
function resolveWindowsSystemRoot(explicitSystemRoot: string | undefined): string {
if (explicitSystemRoot !== undefined) {
return normalizeWindowsRoot(explicitSystemRoot) ?? DEFAULT_WINDOWS_SYSTEM_ROOT;
}
return (
normalizeWindowsRoot(process.env.SystemRoot) ??
normalizeWindowsRoot(process.env.windir) ??
normalizeWindowsRoot(process.env.WINDIR) ??
DEFAULT_WINDOWS_SYSTEM_ROOT
);
}
function getFltmcPathCandidates(systemRoot: string, arch: string): string[] {
const systemDirs = arch === 'ia32' ? ['Sysnative', 'System32'] : ['System32'];
return systemDirs.map((dir) => pathWin32.join(systemRoot, dir, 'fltmc.exe'));
}
function runFltmc(command: string, options: WindowsElevationCommandOptions) {
@ -105,7 +170,8 @@ export function createWindowsElevationStatusChecker(
options: WindowsElevationStatusCheckerOptions = {}
): () => Promise<WindowsElevationStatus> {
const platform = options.platform ?? process.platform;
const systemRoot = options.systemRoot ?? process.env.SystemRoot ?? DEFAULT_WINDOWS_SYSTEM_ROOT;
const arch = options.arch ?? process.arch;
const systemRoot = resolveWindowsSystemRoot(options.systemRoot);
const timeoutMs = options.timeoutMs ?? DEFAULT_WINDOWS_ELEVATION_TIMEOUT_MS;
const runCommand = options.runCommand ?? runFltmc;
@ -114,24 +180,36 @@ export function createWindowsElevationStatusChecker(
return createStatus(platform, null, false);
}
let result: WindowsElevationCommandResult;
try {
result = await runCommand(getFltmcPath(systemRoot), { timeoutMs });
} catch (error) {
return createStatus(platform, null, true, getErrorMessage(error, null));
}
for (const command of getFltmcPathCandidates(systemRoot, arch)) {
let result: WindowsElevationCommandResult;
try {
result = await runCommand(command, { timeoutMs });
} catch (error) {
if (isMissingCommand(error)) {
continue;
}
return createStatus(platform, null, true, getErrorMessage(error, null));
}
if (!result.error) {
return createStatus(platform, true, false);
}
if (!result.error) {
return createStatus(platform, true, false);
}
const message = getErrorMessage(result.error, result.stderr);
if (isMissingCommand(result.error)) {
continue;
}
if (wasKilledOrTimedOut(result.error)) {
return createStatus(platform, null, true, message);
}
if (looksLikeNonElevatedFltmcError(result.error, result.stderr)) {
return createStatus(platform, false, false, message);
}
const code = getErrorCode(result.error);
const message = getErrorMessage(result.error, result.stderr);
if (code === 'ENOENT' || wasKilledOrTimedOut(result.error)) {
return createStatus(platform, null, true, message);
}
return createStatus(platform, false, false, message);
return createStatus(platform, null, true, 'Windows elevation probe command was not found.');
};
}

View file

@ -19,7 +19,22 @@ function createStatus(overrides: Partial<WindowsElevationStatus> = {}): WindowsE
}
function installElevationStatus(status: WindowsElevationStatus) {
const getWindowsElevationStatus = vi.fn().mockResolvedValue(status);
return installElevationStatusPromise(Promise.resolve(status));
}
function installElevationStatusPromise(promise: Promise<WindowsElevationStatus>) {
const getWindowsElevationStatus = vi.fn().mockReturnValue(promise);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
getWindowsElevationStatus,
},
});
return getWindowsElevationStatus;
}
function installElevationStatusFailure() {
const getWindowsElevationStatus = vi.fn().mockRejectedValue(new Error('IPC failed'));
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
@ -110,6 +125,73 @@ describe('WindowsAdministratorBanner', () => {
});
});
it('hides the warning when the status check is inconclusive', async () => {
const getWindowsElevationStatus = installElevationStatus(
createStatus({ isAdministrator: null, checkFailed: true, error: 'probe unavailable' })
);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(WindowsAdministratorBanner));
await flushReact();
});
expect(getWindowsElevationStatus).toHaveBeenCalledTimes(1);
expect(host.textContent).toBe('');
await act(async () => {
root.unmount();
await flushReact();
});
});
it('hides the warning when the status check rejects', async () => {
const getWindowsElevationStatus = installElevationStatusFailure();
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(WindowsAdministratorBanner));
await flushReact();
});
expect(getWindowsElevationStatus).toHaveBeenCalledTimes(1);
expect(host.textContent).toBe('');
await act(async () => {
root.unmount();
await flushReact();
});
});
it('does not render stale status after unmount', async () => {
let resolveStatus: ((status: WindowsElevationStatus) => void) | null = null;
const pendingStatus = new Promise<WindowsElevationStatus>((resolve) => {
resolveStatus = resolve;
});
const getWindowsElevationStatus = installElevationStatusPromise(pendingStatus);
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(WindowsAdministratorBanner));
await flushReact();
});
await act(async () => {
root.unmount();
resolveStatus?.(createStatus());
await flushReact();
});
expect(getWindowsElevationStatus).toHaveBeenCalledTimes(1);
expect(host.textContent).toBe('');
});
it('hides the warning when the preload bridge does not expose the status check', async () => {
Object.defineProperty(window, 'electronAPI', {
configurable: true,

View file

@ -1096,6 +1096,7 @@ describe('AgentTeamsRuntimeProviderManagementCliClient', () => {
expect.arrayContaining(['runtime', 'providers', 'view']),
expect.any(Object)
);
expect(execCliMock.mock.calls[0]?.[2]).toMatchObject({ timeout: 90_000 });
});
it('explains OpenCode CLI help output instead of returning a generic JSON error', async () => {
@ -1274,7 +1275,7 @@ describe('AgentTeamsRuntimeProviderManagementCliClient', () => {
apiKey: 'sk-input-secret-value-123456',
});
await vi.advanceTimersByTimeAsync(45_000);
await vi.advanceTimersByTimeAsync(90_000);
const response = await responsePromise;
vi.useRealTimers();

View file

@ -350,7 +350,7 @@ describe('ClaudeMultimodelBridgeService', () => {
if (normalizedArgs === 'runtime status --json --provider codex --summary') {
return Promise.reject(
new Error(
'Command timed out after 25000ms: /mock/agent_teams_orchestrator runtime status --json --provider codex --summary'
'Command timed out after 30000ms: /mock/agent_teams_orchestrator runtime status --json --provider codex --summary'
)
);
}
@ -370,7 +370,7 @@ describe('ClaudeMultimodelBridgeService', () => {
verificationState: 'error',
statusMessage: 'Provider status unavailable',
});
expect(provider.detailMessage).toContain('Command timed out after 25000ms');
expect(provider.detailMessage).toContain('Command timed out after 30000ms');
expect(calls).toEqual(['runtime status --json --provider codex --summary']);
expect(vi.mocked(console.warn).mock.calls.map((call) => call.join(' '))).toEqual([
expect.stringContaining(
@ -386,7 +386,7 @@ describe('ClaudeMultimodelBridgeService', () => {
if (normalizedArgs === 'runtime status --json --provider opencode --summary') {
return Promise.reject(
new Error(
'Command timed out after 25000ms: /mock/agent_teams_orchestrator runtime status --json --provider opencode --summary'
'Command timed out after 30000ms: /mock/agent_teams_orchestrator runtime status --json --provider opencode --summary'
)
);
}
@ -412,7 +412,7 @@ describe('ClaudeMultimodelBridgeService', () => {
'not necessarily that OpenCode auth is missing'
);
expect(provider.detailMessage).toContain('provider/model inventory');
expect(provider.detailMessage).toContain('Raw timeout detail: Command timed out after 25000ms');
expect(provider.detailMessage).toContain('Raw timeout detail: Command timed out after 30000ms');
expect(execCliMock.mock.calls.map((call) => call[1].join(' '))).toEqual([
'runtime status --json --provider opencode --summary',
]);
@ -509,9 +509,9 @@ describe('ClaudeMultimodelBridgeService', () => {
expect(execCliMock).toHaveBeenCalledTimes(3);
expect(execCliMock.mock.calls.map((call) => call[2]?.timeout)).toEqual([
15000,
15000,
15000,
30000,
30000,
30000,
]);
expect(calls).toEqual([
'runtime status --json --provider anthropic --summary',
@ -895,6 +895,16 @@ describe('ClaudeMultimodelBridgeService', () => {
defaultModelId: 'gpt-5.4',
},
});
expect(
execCliMock.mock.calls.find(
(call) => call[1].join(' ') === 'runtime status --json --provider codex --summary'
)?.[2]?.timeout
).toBe(30_000);
expect(
execCliMock.mock.calls.find(
(call) => call[1].join(' ') === 'runtime status --json --provider codex'
)?.[2]?.timeout
).toBe(90_000);
});
it('queues fresh single-provider catalog hydration behind an in-flight one', async () => {

View file

@ -14,9 +14,24 @@ function createError(
return Object.assign(new Error(message), fields);
}
const originalSystemRoot = process.env.SystemRoot;
const originalWindir = process.env.windir;
const originalWINDIR = process.env.WINDIR;
function restoreEnvValue(name: 'SystemRoot' | 'windir' | 'WINDIR', value: string | undefined): void {
if (value === undefined) {
delete process.env[name];
} else {
process.env[name] = value;
}
}
describe('windowsElevation', () => {
afterEach(() => {
resetWindowsElevationStatusCacheForTests();
restoreEnvValue('SystemRoot', originalSystemRoot);
restoreEnvValue('windir', originalWindir);
restoreEnvValue('WINDIR', originalWINDIR);
});
it('does not run the elevation command outside Windows', async () => {
@ -72,6 +87,145 @@ describe('windowsElevation', () => {
expect(status.error).toBe('Access is denied.');
});
it('reports non-elevated Windows when fltmc returns an access-denied message', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command failed', { code: 'EPERM' }),
stderr: 'The requested operation requires elevation.',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBe(false);
expect(status.checkFailed).toBe(false);
});
it('reports non-elevated Windows when stderr is a Buffer', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command failed', { code: 'EPERM' }),
stderr: Buffer.from('Access is denied.', 'utf8'),
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBe(false);
expect(status.error).toBe('Access is denied.');
});
it('uses the error message when stderr is empty', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('operation not permitted', { code: 'EPERM' }),
stderr: '',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBe(false);
expect(status.error).toBe('operation not permitted');
});
it('reports non-elevated Windows when fltmc returns Windows access-denied code 5', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command failed', { code: 5 }),
stderr: '',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBe(false);
expect(status.checkFailed).toBe(false);
});
it('passes a custom timeout to the Windows probe command', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
await createWindowsElevationStatusChecker({
platform: 'win32',
timeoutMs: 750,
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('C:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 750,
});
});
it('reports an unknown status when fltmc fails for an unrelated reason', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Unexpected fltmc failure', { code: 2 }),
stderr: 'The system cannot find the file specified.',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBeNull();
expect(status.checkFailed).toBe(true);
expect(status.error).toBe('The system cannot find the file specified.');
});
it('caps long probe error text before returning it to the renderer', async () => {
const longError = 'x'.repeat(600);
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Unexpected fltmc failure', { code: 2 }),
stderr: longError,
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBeNull();
expect(status.error).toHaveLength(500);
});
it('does not treat code 1 as non-elevated when the probe executable is unavailable', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command failed', { code: 1 }),
stderr: 'The system cannot find the file specified.',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBeNull();
expect(status.checkFailed).toBe(true);
expect(status.error).toBe('The system cannot find the file specified.');
});
it('does not treat code 1 as non-elevated when Windows cannot recognize the probe command', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command failed', { code: 1 }),
stderr: "'fltmc.exe' is not recognized as an internal or external command.",
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(status.isAdministrator).toBeNull();
expect(status.checkFailed).toBe(true);
});
it('reports an unknown status when the Windows probe command is missing', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('spawn fltmc.exe ENOENT', { code: 'ENOENT' }),
@ -84,7 +238,23 @@ describe('windowsElevation', () => {
expect(status.isAdministrator).toBeNull();
expect(status.checkFailed).toBe(true);
expect(status.error).toContain('ENOENT');
expect(status.error).toBe('Windows elevation probe command was not found.');
});
it('continues to the next path when the probe command throws ENOENT', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockRejectedValueOnce(createError('spawn fltmc.exe ENOENT', { code: 'ENOENT' }))
.mockResolvedValueOnce({ error: null });
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
arch: 'ia32',
runCommand,
})();
expect(runCommand).toHaveBeenCalledTimes(2);
expect(status.isAdministrator).toBe(true);
});
it('reports an unknown status when the Windows probe times out', async () => {
@ -102,6 +272,170 @@ describe('windowsElevation', () => {
expect(status.error).toContain('Command timed out');
});
it('does not try the System32 fallback after a Sysnative timeout', async () => {
const runCommand = vi.fn<WindowsElevationCommandRunner>().mockResolvedValue({
error: createError('Command timed out', { code: 'ETIMEDOUT', killed: true }),
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
arch: 'ia32',
runCommand,
})();
expect(runCommand).toHaveBeenCalledTimes(1);
expect(status.isAdministrator).toBeNull();
expect(status.checkFailed).toBe(true);
});
it('tries the Sysnative fltmc path first for 32-bit Windows processes', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValueOnce({
error: createError('spawn fltmc.exe ENOENT', { code: 'ENOENT' }),
})
.mockResolvedValueOnce({ error: null });
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
arch: 'ia32',
systemRoot: 'C:\\Windows',
runCommand,
})();
expect(runCommand).toHaveBeenNthCalledWith(1, 'C:\\Windows\\Sysnative\\fltmc.exe', {
timeoutMs: 3_000,
});
expect(runCommand).toHaveBeenNthCalledWith(2, 'C:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
expect(status.isAdministrator).toBe(true);
});
it('uses only System32 for non-32-bit Windows processes', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
arch: 'arm64',
systemRoot: 'C:\\Windows',
runCommand,
})();
expect(runCommand).toHaveBeenCalledTimes(1);
expect(runCommand).toHaveBeenCalledWith('C:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
expect(status.isAdministrator).toBe(true);
});
it('continues from missing Sysnative to a non-elevated System32 result', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValueOnce({
error: createError('spawn fltmc.exe ENOENT', { code: 'ENOENT' }),
})
.mockResolvedValueOnce({
error: createError('Command failed', { code: 5 }),
stderr: '',
});
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
arch: 'ia32',
runCommand,
})();
expect(runCommand).toHaveBeenCalledTimes(2);
expect(status.isAdministrator).toBe(false);
});
it('falls back to the default Windows root when SystemRoot is empty', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
const status = await createWindowsElevationStatusChecker({
platform: 'win32',
systemRoot: '',
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('C:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
expect(status.isAdministrator).toBe(true);
});
it('normalizes quoted Windows root values', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
await createWindowsElevationStatusChecker({
platform: 'win32',
systemRoot: ' "D:\\Windows" ',
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('D:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
});
it('falls back to windir when SystemRoot is unavailable', async () => {
delete process.env.SystemRoot;
process.env.windir = 'E:\\WinDir';
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('E:\\WinDir\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
});
it('falls back to uppercase WINDIR when SystemRoot and windir are unavailable', async () => {
delete process.env.SystemRoot;
delete process.env.windir;
process.env.WINDIR = 'F:\\Windows';
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
await createWindowsElevationStatusChecker({
platform: 'win32',
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('F:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
});
it('falls back to the default Windows root for relative SystemRoot values', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()
.mockResolvedValue({ error: null });
await createWindowsElevationStatusChecker({
platform: 'win32',
systemRoot: 'Windows',
runCommand,
})();
expect(runCommand).toHaveBeenCalledWith('C:\\Windows\\System32\\fltmc.exe', {
timeoutMs: 3_000,
});
});
it('reports an unknown status when the Windows probe throws before returning a result', async () => {
const runCommand = vi
.fn<WindowsElevationCommandRunner>()

View file

@ -0,0 +1,176 @@
// @vitest-environment node
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({
execFile: vi.fn(),
}));
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
...actual,
execFile: mocks.execFile,
};
});
type ExecFileCallback = (
error: Error | null,
stdout: string | Buffer,
stderr: string | Buffer
) => void;
function createError(
message: string,
fields: { code?: string | number; killed?: boolean; signal?: string | null } = {}
): Error & { code?: string | number; killed?: boolean; signal?: string | null } {
return Object.assign(new Error(message), fields);
}
function setPlatform(value: string): void {
Object.defineProperty(process, 'platform', {
value,
configurable: true,
});
}
function setArch(value: string): void {
Object.defineProperty(process, 'arch', {
value,
configurable: true,
});
}
const originalPlatform = process.platform;
const originalArch = process.arch;
const originalSystemRoot = process.env.SystemRoot;
const originalWindir = process.env.windir;
const originalWINDIR = process.env.WINDIR;
function restoreEnvValue(name: 'SystemRoot' | 'windir' | 'WINDIR', value: string | undefined): void {
if (value === undefined) {
delete process.env[name];
} else {
process.env[name] = value;
}
}
async function importRuntime() {
return import('@main/utils/windowsElevation');
}
describe('windowsElevation runtime integration', () => {
beforeEach(() => {
vi.resetModules();
mocks.execFile.mockReset();
setPlatform(originalPlatform);
setArch(originalArch);
restoreEnvValue('SystemRoot', originalSystemRoot);
restoreEnvValue('windir', originalWindir);
restoreEnvValue('WINDIR', originalWINDIR);
});
afterEach(() => {
setPlatform(originalPlatform);
setArch(originalArch);
restoreEnvValue('SystemRoot', originalSystemRoot);
restoreEnvValue('windir', originalWindir);
restoreEnvValue('WINDIR', originalWINDIR);
vi.restoreAllMocks();
});
it('does not invoke execFile when the process platform is not Windows', async () => {
setPlatform('linux');
const { getWindowsElevationStatus } = await importRuntime();
const status = await getWindowsElevationStatus();
expect(mocks.execFile).not.toHaveBeenCalled();
expect(status).toEqual({
platform: 'linux',
isWindows: false,
isAdministrator: null,
checkFailed: false,
error: null,
});
});
it('runs fltmc with hidden-window timeout options on Windows', async () => {
setPlatform('win32');
setArch('x64');
process.env.SystemRoot = 'C:\\Windows';
mocks.execFile.mockImplementation(
(_command: string, _args: string[], _options: unknown, callback: ExecFileCallback) => {
callback(null, '', '');
}
);
const { getWindowsElevationStatus } = await importRuntime();
const status = await getWindowsElevationStatus();
expect(mocks.execFile).toHaveBeenCalledTimes(1);
expect(mocks.execFile).toHaveBeenCalledWith(
'C:\\Windows\\System32\\fltmc.exe',
[],
{ timeout: 3_000, windowsHide: true },
expect.any(Function)
);
expect(status.isAdministrator).toBe(true);
});
it('coalesces concurrent status requests into one Windows subprocess', async () => {
setPlatform('win32');
setArch('x64');
const captured: { callback?: ExecFileCallback } = {};
mocks.execFile.mockImplementation(
(_command: string, _args: string[], _options: unknown, nextCallback: ExecFileCallback) => {
captured.callback = nextCallback;
}
);
const { getWindowsElevationStatus } = await importRuntime();
const first = getWindowsElevationStatus();
const second = getWindowsElevationStatus();
expect(mocks.execFile).toHaveBeenCalledTimes(1);
expect(captured.callback).toBeTypeOf('function');
captured.callback?.(null, '', '');
await expect(first).resolves.toMatchObject({ isAdministrator: true });
await expect(second).resolves.toMatchObject({ isAdministrator: true });
});
it('reuses the cached result after the first Windows probe completes', async () => {
setPlatform('win32');
setArch('x64');
mocks.execFile.mockImplementation(
(_command: string, _args: string[], _options: unknown, callback: ExecFileCallback) => {
callback(createError('Command failed', { code: 5 }), '', '');
}
);
const { getWindowsElevationStatus } = await importRuntime();
const first = await getWindowsElevationStatus();
const second = await getWindowsElevationStatus();
expect(mocks.execFile).toHaveBeenCalledTimes(1);
expect(first).toEqual(second);
expect(second.isAdministrator).toBe(false);
});
it('runs a new probe after the cache is reset for tests', async () => {
setPlatform('win32');
setArch('x64');
mocks.execFile.mockImplementation(
(_command: string, _args: string[], _options: unknown, callback: ExecFileCallback) => {
callback(null, '', '');
}
);
const { getWindowsElevationStatus, resetWindowsElevationStatusCacheForTests } =
await importRuntime();
await getWindowsElevationStatus();
resetWindowsElevationStatusCacheForTests();
await getWindowsElevationStatus();
expect(mocks.execFile).toHaveBeenCalledTimes(2);
});
});

View file

@ -41,6 +41,7 @@ describe('preload electronAPI memberWorkSync wiring', () => {
vi.resetModules();
vi.useFakeTimers();
mocks.contextBridge.exposeInMainWorld.mockClear();
mocks.ipcRenderer.invoke.mockClear();
mocks.createMemberWorkSyncBridge.mockClear();
});
@ -64,4 +65,24 @@ describe('preload electronAPI memberWorkSync wiring', () => {
expect(apiName).toBe('electronAPI');
expect(electronAPI.memberWorkSync).toBe(mocks.memberWorkSyncBridge);
});
it('wires the Windows elevation status API to the app IPC channel', async () => {
await import('../../src/preload/index');
const [, electronAPI] = mocks.contextBridge.exposeInMainWorld.mock.calls[0] as [
string,
ElectronAPI,
];
const expectedStatus = {
platform: 'win32',
isWindows: true,
isAdministrator: false,
checkFailed: false,
error: null,
};
mocks.ipcRenderer.invoke.mockResolvedValueOnce(expectedStatus);
await expect(electronAPI.getWindowsElevationStatus()).resolves.toBe(expectedStatus);
expect(mocks.ipcRenderer.invoke).toHaveBeenCalledWith('app:getWindowsElevationStatus');
});
});