Add unit tests for skill capabilities, skill planner, and UV setup
- Implement tests for random ID generation, ensuring preference for crypto.randomUUID. - Create tests for runtime context capabilities, validating the injection of enabled skill capabilities. - Add tests for skill capability parsing, including classification and command example extraction. - Introduce tests for the skill planner, verifying tool call planning based on user requests and attachment requirements. - Establish tests for UV setup, ensuring proper handling of Python installation scenarios and environment checks.
This commit is contained in:
@@ -5,6 +5,14 @@ import { app } from 'electron';
|
||||
import logManager from '@electron/service/logger';
|
||||
import { getUvMirrorEnv } from './uv-env';
|
||||
|
||||
type UvResolutionSource = 'bundled' | 'path' | 'bundled-fallback' | 'missing';
|
||||
|
||||
type UvResolution = {
|
||||
bin: string;
|
||||
source: UvResolutionSource;
|
||||
bundledPath: string;
|
||||
};
|
||||
|
||||
function getBundledUvPath(): string {
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
@@ -18,35 +26,54 @@ function getBundledUvPath(): string {
|
||||
return join(process.cwd(), 'resources', 'bin', target, binName);
|
||||
}
|
||||
|
||||
function findUvInPathSync(): boolean {
|
||||
function findUvInPathSync(): string | null {
|
||||
try {
|
||||
const command = process.platform === 'win32' ? 'where.exe uv' : 'which uv';
|
||||
execSync(command, { stdio: 'ignore', timeout: 5000, windowsHide: true });
|
||||
return true;
|
||||
const output = execSync(command, {
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const resolved = output
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
return resolved || null;
|
||||
} catch {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveUvBin(): { bin: string; source: 'bundled' | 'path' | 'bundled-fallback' } {
|
||||
function resolveUvBin(): UvResolution {
|
||||
const bundled = getBundledUvPath();
|
||||
|
||||
if (app.isPackaged) {
|
||||
if (existsSync(bundled)) {
|
||||
return { bin: bundled, source: 'bundled' };
|
||||
return { bin: bundled, source: 'bundled', bundledPath: bundled };
|
||||
}
|
||||
logManager.warn(`Bundled uv binary not found at ${bundled}, falling back to system PATH`);
|
||||
}
|
||||
|
||||
if (findUvInPathSync()) {
|
||||
return { bin: 'uv', source: 'path' };
|
||||
const fromPath = findUvInPathSync();
|
||||
if (fromPath) {
|
||||
return { bin: fromPath, source: 'path', bundledPath: bundled };
|
||||
}
|
||||
|
||||
if (existsSync(bundled)) {
|
||||
return { bin: bundled, source: 'bundled-fallback' };
|
||||
return { bin: bundled, source: 'bundled-fallback', bundledPath: bundled };
|
||||
}
|
||||
|
||||
return { bin: 'uv', source: 'path' };
|
||||
return { bin: bundled, source: 'missing', bundledPath: bundled };
|
||||
}
|
||||
|
||||
function buildMissingUvError(resolution: UvResolution): Error {
|
||||
return new Error(
|
||||
`uv is required for managed Python setup but is unavailable.\n` +
|
||||
` expected bundled binary: ${resolution.bundledPath}\n` +
|
||||
` platform: ${process.platform}/${process.arch}\n` +
|
||||
` searched PATH: yes`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function checkUvInstalled(): Promise<boolean> {
|
||||
@@ -54,20 +81,26 @@ export async function checkUvInstalled(): Promise<boolean> {
|
||||
if (source === 'bundled' || source === 'bundled-fallback') {
|
||||
return existsSync(bin);
|
||||
}
|
||||
return findUvInPathSync();
|
||||
if (source === 'path') {
|
||||
return Boolean(bin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function installUv(): Promise<void> {
|
||||
const isAvailable = await checkUvInstalled();
|
||||
if (!isAvailable) {
|
||||
const bin = getBundledUvPath();
|
||||
throw new Error(`uv not found in system PATH and bundled binary missing at ${bin}`);
|
||||
const resolution = resolveUvBin();
|
||||
if (resolution.source === 'missing') {
|
||||
throw buildMissingUvError(resolution);
|
||||
}
|
||||
logManager.info('uv is available and ready to use');
|
||||
}
|
||||
|
||||
export async function isPythonReady(): Promise<boolean> {
|
||||
const { bin: uvBin } = resolveUvBin();
|
||||
const resolution = resolveUvBin();
|
||||
if (resolution.source === 'missing') {
|
||||
return false;
|
||||
}
|
||||
const uvBin = resolution.bin;
|
||||
|
||||
return await new Promise<boolean>((resolve) => {
|
||||
try {
|
||||
@@ -140,7 +173,11 @@ async function runPythonInstall(
|
||||
}
|
||||
|
||||
export async function setupManagedPython(): Promise<void> {
|
||||
const { bin: uvBin, source } = resolveUvBin();
|
||||
const resolution = resolveUvBin();
|
||||
if (resolution.source === 'missing') {
|
||||
throw buildMissingUvError(resolution);
|
||||
}
|
||||
const { bin: uvBin, source } = resolution;
|
||||
const uvEnv = await getUvMirrorEnv();
|
||||
const hasMirror = Object.keys(uvEnv).length > 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user