agent-ecosystem/test/main/utils/runtimePathBinaryResolver.test.ts
2026-05-22 00:17:54 +03:00

99 lines
3.4 KiB
TypeScript

import { chmod, mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const buildMergedCliPathMock = vi.hoisted(() => vi.fn(() => ''));
const getCachedShellEnvMock = vi.hoisted(() => vi.fn<() => NodeJS.ProcessEnv | null>(() => null));
vi.mock('@main/utils/cliPathMerge', () => ({
buildMergedCliPath: buildMergedCliPathMock,
}));
vi.mock('@main/utils/shellEnv', () => ({
getCachedShellEnv: getCachedShellEnvMock,
}));
import {
collectRuntimePathBinaryCandidates,
findFirstRuntimePathBinaryCandidate,
RUNTIME_PATH_SHELL_ENV_TIMEOUT_MS,
} from '@main/utils/runtimePathBinaryResolver';
describe('runtimePathBinaryResolver', () => {
let tempRoot: string | null = null;
let originalPath: string | undefined;
beforeEach(async () => {
tempRoot = await mkdtemp(path.join(os.tmpdir(), 'runtime-path-binary-resolver-'));
originalPath = process.env.PATH;
process.env.PATH = '';
buildMergedCliPathMock.mockReset();
buildMergedCliPathMock.mockReturnValue('');
getCachedShellEnvMock.mockReset();
getCachedShellEnvMock.mockReturnValue(null);
});
afterEach(async () => {
if (originalPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = originalPath;
}
if (tempRoot) {
await rm(tempRoot, { recursive: true, force: true });
tempRoot = null;
}
});
async function createExecutable(dirName: string, name: string): Promise<string> {
const binaryPath = path.join(tempRoot!, dirName, name);
await mkdir(path.dirname(binaryPath), { recursive: true });
await writeFile(binaryPath, 'binary');
if (process.platform !== 'win32') {
await chmod(binaryPath, 0o755);
}
return binaryPath;
}
it('prefers explicit env sources before cached and fallback PATH entries', async () => {
const explicitBinary = await createExecutable('explicit-bin', 'tool');
const cachedBinary = await createExecutable('cached-bin', 'tool');
const fallbackBinary = await createExecutable('fallback-bin', 'tool');
getCachedShellEnvMock.mockReturnValue({ PATH: path.dirname(cachedBinary) });
buildMergedCliPathMock.mockReturnValue(path.dirname(fallbackBinary));
expect(
findFirstRuntimePathBinaryCandidate({
executableNames: ['tool'],
additionalEnvSources: [{ PATH: path.dirname(explicitBinary) }],
})
).toBe(explicitBinary);
});
it('keeps extra candidates before fallback PATH entries and filters missing files', async () => {
const extraBinary = await createExecutable('extra-bin', 'tool');
const fallbackBinary = await createExecutable('fallback-bin', 'tool');
buildMergedCliPathMock.mockReturnValue(path.dirname(fallbackBinary));
expect(
collectRuntimePathBinaryCandidates({
executableNames: ['tool'],
extraCandidates: [path.join(tempRoot!, 'missing', 'tool'), extraBinary],
})
).toEqual([extraBinary, fallbackBinary]);
});
it('can skip fallback PATH entries for staged shell-env lookup', async () => {
const fallbackBinary = await createExecutable('fallback-bin', 'tool');
buildMergedCliPathMock.mockReturnValue(path.dirname(fallbackBinary));
expect(
collectRuntimePathBinaryCandidates({
executableNames: ['tool'],
includeFallbackPathEntries: false,
})
).toEqual([]);
expect(RUNTIME_PATH_SHELL_ENV_TIMEOUT_MS).toBe(1_500);
});
});