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:
DEV_DSW
2026-04-24 17:02:59 +08:00
parent e11a2296cc
commit 4c61e93c3e
42 changed files with 12560 additions and 224 deletions

View File

@@ -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;