Files
2026-07-13 13:20:22 +08:00

148 lines
4.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
buildWindowsHydratedPath,
hydrateWindowsProcessPath,
parseWindowsProfilePathEntries,
parseWindowsRegistryPathOutput,
} from '@/process/startup/windowsPath';
const windowsOnlyIt = process.platform === 'win32' ? it : it.skip;
describe('parseWindowsRegistryPathOutput', () => {
it('extracts and expands the Path value from reg.exe output', () => {
const output = `
HKEY_CURRENT_USER\\Environment
Path REG_EXPAND_SZ D:\\AgentBin;%USERPROFILE%\\AppData\\Roaming\\npm
`;
expect(
parseWindowsRegistryPathOutput(output, {
USERPROFILE: 'C:\\Users\\zhoukai',
})
).toEqual(['D:\\AgentBin', 'C:\\Users\\zhoukai\\AppData\\Roaming\\npm']);
});
it('returns an empty list when reg.exe does not return a Path value', () => {
const output = `
HKEY_CURRENT_USER\\Environment
TEMP REG_SZ C:\\Temp
`;
expect(parseWindowsRegistryPathOutput(output, {})).toEqual([]);
});
});
describe('parseWindowsProfilePathEntries', () => {
it('extracts PATH additions from PowerShell profile snippets', () => {
const content = `
$env:Path += ';D:\\Portable\\opencode\\bin'
$env:PATH = "$env:USERPROFILE\\.local\\bin;$env:PATH"
`;
expect(
parseWindowsProfilePathEntries(content, {
USERPROFILE: 'C:\\Users\\zhoukai',
})
).toEqual(['D:\\Portable\\opencode\\bin', 'C:\\Users\\zhoukai\\.local\\bin']);
});
it('extracts PATH additions from Git Bash profile snippets', () => {
const content = `
export PATH="$HOME/.bun/bin:/d/AgentBin:$PATH"
`;
expect(
parseWindowsProfilePathEntries(content, {
HOME: 'C:\\Users\\zhoukai',
})
).toEqual(['C:\\Users\\zhoukai\\.bun\\bin', 'D:\\AgentBin']);
});
});
describe('buildWindowsHydratedPath', () => {
it('merges missing user and machine registry paths into the current process PATH', () => {
const hydrated = buildWindowsHydratedPath({
currentPath: 'C:\\Windows\\System32;C:\\Users\\zhoukai\\AppData\\Roaming\\npm',
userRegistryOutput: `
HKEY_CURRENT_USER\\Environment
Path REG_EXPAND_SZ D:\\AgentBin;%USERPROFILE%\\AppData\\Roaming\\npm
`,
machineRegistryOutput: `
HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment
Path REG_EXPAND_SZ C:\\Program Files\\Git\\cmd;C:\\Windows\\System32
`,
profileContents: [],
fallbackPathEntries: [],
env: {
USERPROFILE: 'C:\\Users\\zhoukai',
},
});
expect(hydrated).toBe(
'C:\\Program Files\\Git\\cmd;C:\\Windows\\System32;D:\\AgentBin;C:\\Users\\zhoukai\\AppData\\Roaming\\npm'
);
});
it('merges shell profile and fallback paths when they are not in registry PATH', () => {
const hydrated = buildWindowsHydratedPath({
currentPath: 'C:\\Windows\\System32',
userRegistryOutput: '',
machineRegistryOutput: '',
profileContents: [`$env:Path += ';D:\\Portable\\opencode\\bin'`],
fallbackPathEntries: ['C:\\Users\\zhoukai\\AppData\\Roaming\\npm', 'D:\\Portable\\opencode\\bin'],
env: {
USERPROFILE: 'C:\\Users\\zhoukai',
},
});
expect(hydrated).toBe(
'D:\\Portable\\opencode\\bin;C:\\Users\\zhoukai\\AppData\\Roaming\\npm;C:\\Windows\\System32'
);
});
windowsOnlyIt('hydrates PATH from real Windows profile files and existing fallback directories', () => {
const root = mkdtempSync(path.join(tmpdir(), 'aionui-windows-path-'));
const userProfile = path.join(root, 'User');
const appData = path.join(userProfile, 'AppData', 'Roaming');
const localAppData = path.join(userProfile, 'AppData', 'Local');
const profileDir = path.join(userProfile, 'Documents', 'PowerShell');
const profileOnlyBin = path.join(userProfile, 'portable-opencode', 'bin');
const npmBin = path.join(appData, 'npm');
const windowsApps = path.join(localAppData, 'Microsoft', 'WindowsApps');
try {
mkdirSync(profileOnlyBin, { recursive: true });
mkdirSync(npmBin, { recursive: true });
mkdirSync(windowsApps, { recursive: true });
mkdirSync(profileDir, { recursive: true });
writeFileSync(
path.join(profileDir, 'Microsoft.PowerShell_profile.ps1'),
`$env:Path += ';${profileOnlyBin}'\n`,
'utf8'
);
const env: NodeJS.ProcessEnv = {
Path: 'C:\\Windows\\System32',
USERPROFILE: userProfile,
APPDATA: appData,
LOCALAPPDATA: localAppData,
};
const hydrated = hydrateWindowsProcessPath(env);
expect(hydrated.split(';')).toEqual(expect.arrayContaining([profileOnlyBin, npmBin, windowsApps]));
expect(env.PATH).toBe(hydrated);
expect(env.Path).toBe(hydrated);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});