mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-20 19:03:51 +08:00
feat: add MCP server for semantic code search with FastMCP integration
This commit is contained in:
@@ -1,120 +0,0 @@
|
||||
/**
|
||||
* Regression test: CodexLens bootstrap should recover when UV bootstrap fails
|
||||
* and the existing venv is missing pip (common with UV-created venvs).
|
||||
*
|
||||
* We simulate "UV available but broken" by pointing CCW_UV_PATH to the current Node
|
||||
* executable. `node --version` exits 0 so isUvAvailable() returns true, but any
|
||||
* `node pip install ...` calls fail, forcing bootstrapVenv() to fall back to pip.
|
||||
*
|
||||
* Before running bootstrapVenv(), we pre-create the venv and delete its pip entrypoint
|
||||
* to mimic a venv that has Python but no pip executable. bootstrapVenv() should
|
||||
* re-bootstrap pip (ensurepip) or recreate the venv, then succeed.
|
||||
*/
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// repo root: <repo>/ccw/tests -> <repo>
|
||||
const REPO_ROOT = join(__dirname, '..', '..');
|
||||
|
||||
function runNodeEvalModule(script, env) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, ['--input-type=module', '-e', script], {
|
||||
cwd: REPO_ROOT,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
||||
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
||||
|
||||
child.on('error', (err) => reject(err));
|
||||
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
||||
});
|
||||
}
|
||||
|
||||
describe('CodexLens bootstrap pip repair', () => {
|
||||
it('repairs missing pip in existing venv during pip fallback', { timeout: 10 * 60 * 1000 }, async () => {
|
||||
const dataDir = mkdtempSync(join(tmpdir(), 'codexlens-bootstrap-pip-missing-'));
|
||||
|
||||
try {
|
||||
const script = `
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, rmSync, mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { getSystemPython } from './ccw/dist/utils/python-utils.js';
|
||||
import { bootstrapVenv } from './ccw/dist/tools/codex-lens.js';
|
||||
|
||||
const dataDir = process.env.CODEXLENS_DATA_DIR;
|
||||
if (!dataDir) throw new Error('Missing CODEXLENS_DATA_DIR');
|
||||
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
const venvDir = join(dataDir, 'venv');
|
||||
|
||||
// Create a venv up-front so UV bootstrap will skip venv creation and fail on install.
|
||||
const pythonCmd = getSystemPython();
|
||||
execSync(pythonCmd + ' -m venv "' + venvDir + '"', { stdio: 'inherit' });
|
||||
|
||||
// Simulate a "pip-less" venv by deleting the pip entrypoint.
|
||||
const pipPath = process.platform === 'win32'
|
||||
? join(venvDir, 'Scripts', 'pip.exe')
|
||||
: join(venvDir, 'bin', 'pip');
|
||||
if (existsSync(pipPath)) {
|
||||
rmSync(pipPath, { force: true });
|
||||
}
|
||||
|
||||
const result = await bootstrapVenv();
|
||||
const pipRestored = existsSync(pipPath);
|
||||
|
||||
console.log('@@RESULT@@' + JSON.stringify({ result, pipRestored }));
|
||||
`.trim();
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
// Isolate test venv + dependencies from user/global CodexLens state.
|
||||
CODEXLENS_DATA_DIR: dataDir,
|
||||
// Make isUvAvailable() return true, but installFromProject() fail.
|
||||
CCW_UV_PATH: process.execPath,
|
||||
};
|
||||
|
||||
const { code, stdout, stderr } = await runNodeEvalModule(script, env);
|
||||
assert.equal(code, 0, `bootstrapVenv child process failed:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`);
|
||||
|
||||
const marker = '@@RESULT@@';
|
||||
const idx = stdout.lastIndexOf(marker);
|
||||
assert.ok(idx !== -1, `Missing result marker in stdout:\n${stdout}`);
|
||||
|
||||
const jsonText = stdout.slice(idx + marker.length).trim();
|
||||
const parsed = JSON.parse(jsonText);
|
||||
|
||||
assert.equal(parsed?.result?.success, true, `Expected success=true, got:\n${jsonText}`);
|
||||
assert.equal(parsed?.pipRestored, true, `Expected pipRestored=true, got:\n${jsonText}`);
|
||||
|
||||
// Best-effort: confirm we exercised the missing-pip repair path.
|
||||
assert.ok(
|
||||
String(stderr).includes('pip not found at:') || String(stdout).includes('pip not found at:'),
|
||||
`Expected missing-pip warning in output. STDERR:\n${stderr}\nSTDOUT:\n${stdout}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Best effort cleanup; leave artifacts only if Windows locks prevent removal.
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
/**
|
||||
* Integration tests for CodexLens UV installation functionality.
|
||||
*
|
||||
* Notes:
|
||||
* - Targets the runtime implementation shipped in `ccw/dist`.
|
||||
* - Tests real package installation (fastembed, hnswlib, onnxruntime, ccw-litellm, codex-lens).
|
||||
* - Verifies Python import success for installed packages.
|
||||
* - Tests UV's dependency conflict auto-resolution capability.
|
||||
* - Uses temporary directories with cleanup after tests.
|
||||
*/
|
||||
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
const uvManagerUrl = new URL('../dist/utils/uv-manager.js', import.meta.url);
|
||||
uvManagerUrl.searchParams.set('t', String(Date.now()));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let mod: any;
|
||||
|
||||
// Test venv path with unique timestamp
|
||||
const TEST_VENV_PATH = join(tmpdir(), `codexlens-install-test-${Date.now()}`);
|
||||
|
||||
// Track UV availability for conditional tests
|
||||
let uvAvailable = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let manager: any;
|
||||
|
||||
describe('CodexLens UV Installation Tests', async () => {
|
||||
mod = await import(uvManagerUrl.href);
|
||||
|
||||
before(async () => {
|
||||
uvAvailable = await mod.isUvAvailable();
|
||||
if (!uvAvailable) {
|
||||
console.log('[Test] UV not available, attempting to install...');
|
||||
uvAvailable = await mod.ensureUvInstalled();
|
||||
}
|
||||
|
||||
if (uvAvailable) {
|
||||
manager = new mod.UvManager({
|
||||
venvPath: TEST_VENV_PATH,
|
||||
pythonVersion: '>=3.10,<3.13', // onnxruntime compatibility range
|
||||
});
|
||||
console.log(`[Test] Created UvManager with venv path: ${TEST_VENV_PATH}`);
|
||||
}
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Clean up test venv
|
||||
if (existsSync(TEST_VENV_PATH)) {
|
||||
console.log(`[Test] Cleaning up test venv: ${TEST_VENV_PATH}`);
|
||||
try {
|
||||
rmSync(TEST_VENV_PATH, { recursive: true, force: true });
|
||||
} catch (err) {
|
||||
console.log(`[Test] Failed to remove venv: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('Virtual Environment Setup', () => {
|
||||
it('should create venv with correct Python version', async () => {
|
||||
if (!uvAvailable) {
|
||||
console.log('[Test] Skipping - UV not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.createVenv();
|
||||
console.log(`[Test] Create venv result:`, result);
|
||||
assert.ok(result.success, `Venv creation failed: ${result.error}`);
|
||||
|
||||
// Verify Python version
|
||||
const version = await manager.getPythonVersion();
|
||||
console.log(`[Test] Python version: ${version}`);
|
||||
const match = version?.match(/3\.(\d+)/);
|
||||
assert.ok(match, 'Should be Python 3.x');
|
||||
const minor = parseInt(match[1]);
|
||||
assert.ok(minor >= 10 && minor < 13, `Python version should be 3.10-3.12, got 3.${minor}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Semantic Search Dependencies (fastembed)', () => {
|
||||
it('should install fastembed and hnswlib', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Test] Installing fastembed and hnswlib...');
|
||||
const startTime = Date.now();
|
||||
|
||||
const result = await manager.install([
|
||||
'numpy>=1.24',
|
||||
'fastembed>=0.5',
|
||||
'hnswlib>=0.8.0',
|
||||
]);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[Test] Installation result:`, result);
|
||||
console.log(`[Test] Installation took ${duration}ms`);
|
||||
|
||||
assert.ok(result.success, `fastembed installation failed: ${result.error}`);
|
||||
});
|
||||
|
||||
it('should verify fastembed is importable', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython([
|
||||
'-c',
|
||||
'import fastembed; print(f"fastembed version: {fastembed.__version__}")',
|
||||
]);
|
||||
|
||||
console.log(`[Test] fastembed import:`, result);
|
||||
assert.ok(result.success, `fastembed import failed: ${result.stderr}`);
|
||||
assert.ok(result.stdout.includes('fastembed version'), 'Should print fastembed version');
|
||||
});
|
||||
|
||||
it('should verify hnswlib is importable', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython(['-c', 'import hnswlib; print("hnswlib imported successfully")']);
|
||||
|
||||
console.log(`[Test] hnswlib import:`, result);
|
||||
assert.ok(result.success, `hnswlib import failed: ${result.stderr}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ONNX Runtime Installation', () => {
|
||||
it('should install onnxruntime (CPU)', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Test] Installing onnxruntime...');
|
||||
const result = await manager.install(['onnxruntime>=1.18.0']);
|
||||
|
||||
console.log(`[Test] onnxruntime installation:`, result);
|
||||
assert.ok(result.success, `onnxruntime installation failed: ${result.error}`);
|
||||
});
|
||||
|
||||
it('should verify onnxruntime providers', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython([
|
||||
'-c',
|
||||
'import onnxruntime; print("Providers:", onnxruntime.get_available_providers())',
|
||||
]);
|
||||
|
||||
console.log(`[Test] onnxruntime providers:`, result);
|
||||
assert.ok(result.success, `onnxruntime import failed: ${result.stderr}`);
|
||||
assert.ok(result.stdout.includes('CPUExecutionProvider'), 'Should have CPU provider');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ccw-litellm Installation', () => {
|
||||
it('should install ccw-litellm from local path', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find local ccw-litellm package
|
||||
const possiblePaths = [join(process.cwd(), 'ccw-litellm'), 'D:\\Claude_dms3\\ccw-litellm'];
|
||||
|
||||
let localPath: string | null = null;
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(join(p, 'pyproject.toml'))) {
|
||||
localPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!localPath) {
|
||||
console.log('[Test] ccw-litellm local path not found, installing from PyPI...');
|
||||
const result = await manager.install(['ccw-litellm']);
|
||||
console.log(`[Test] PyPI installation:`, result);
|
||||
// PyPI may not have it published, skip
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Test] Installing ccw-litellm from: ${localPath}`);
|
||||
const result = await manager.installFromProject(localPath);
|
||||
|
||||
console.log(`[Test] ccw-litellm installation:`, result);
|
||||
assert.ok(result.success, `ccw-litellm installation failed: ${result.error}`);
|
||||
});
|
||||
|
||||
it('should verify ccw-litellm is importable', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython([
|
||||
'-c',
|
||||
'import ccw_litellm; print(f"ccw-litellm version: {ccw_litellm.__version__}")',
|
||||
]);
|
||||
|
||||
console.log(`[Test] ccw-litellm import:`, result);
|
||||
// If installation failed (PyPI doesn't have it), skip validation
|
||||
if (!result.success && result.stderr.includes('No module named')) {
|
||||
console.log('[Test] ccw-litellm not installed, skipping import test');
|
||||
return;
|
||||
}
|
||||
|
||||
assert.ok(result.success, `ccw-litellm import failed: ${result.stderr}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Full codex-lens Installation', () => {
|
||||
it('should install codex-lens with semantic extras from local path', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find local codex-lens package
|
||||
const possiblePaths = [join(process.cwd(), 'codex-lens'), 'D:\\Claude_dms3\\codex-lens'];
|
||||
|
||||
let localPath: string | null = null;
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(join(p, 'pyproject.toml'))) {
|
||||
localPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!localPath) {
|
||||
console.log('[Test] codex-lens local path not found, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Test] Installing codex-lens[semantic] from: ${localPath}`);
|
||||
const startTime = Date.now();
|
||||
|
||||
const result = await manager.installFromProject(localPath, ['semantic']);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[Test] codex-lens installation:`, result);
|
||||
console.log(`[Test] Installation took ${duration}ms`);
|
||||
|
||||
assert.ok(result.success, `codex-lens installation failed: ${result.error}`);
|
||||
});
|
||||
|
||||
it('should verify codex-lens CLI is available', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython(['-m', 'codexlens', '--help']);
|
||||
|
||||
console.log(`[Test] codexlens CLI help output length: ${result.stdout.length}`);
|
||||
// CLI may fail due to dependency issues, log but don't force failure
|
||||
if (!result.success) {
|
||||
console.log(`[Test] codexlens CLI failed: ${result.stderr}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dependency Conflict Resolution', () => {
|
||||
it('should handle onnxruntime version conflicts automatically', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
// UV should auto-resolve conflicts between fastembed and onnxruntime
|
||||
// Install onnxruntime first, then fastembed, verify no errors
|
||||
console.log('[Test] Testing conflict resolution...');
|
||||
|
||||
// Check current onnxruntime version
|
||||
const result = await manager.runPython(['-c', 'import onnxruntime; print(f"onnxruntime: {onnxruntime.__version__}")']);
|
||||
|
||||
console.log(`[Test] Current onnxruntime:`, result.stdout.trim());
|
||||
|
||||
// Reinstall fastembed, UV should handle dependencies
|
||||
const installResult = await manager.install(['fastembed>=0.5']);
|
||||
console.log(`[Test] Reinstall fastembed:`, installResult);
|
||||
|
||||
// Check onnxruntime again
|
||||
const result2 = await manager.runPython(['-c', 'import onnxruntime; print(f"onnxruntime: {onnxruntime.__version__}")']);
|
||||
|
||||
console.log(`[Test] After reinstall onnxruntime:`, result2.stdout.trim());
|
||||
assert.ok(result2.success, 'onnxruntime should still be importable after fastembed reinstall');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Package List Verification', () => {
|
||||
it('should list all installed packages', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const packages = await manager.list();
|
||||
console.log(`[Test] Total installed packages: ${packages?.length ?? 0}`);
|
||||
|
||||
if (packages !== null) {
|
||||
assert.ok(Array.isArray(packages), 'list() should return array');
|
||||
|
||||
// Check for expected packages
|
||||
const packageNames = packages.map((p: { name: string }) => p.name.toLowerCase().replace(/-/g, '_'));
|
||||
console.log(`[Test] Package names: ${packageNames.slice(0, 10).join(', ')}...`);
|
||||
|
||||
// Verify core packages are present
|
||||
const hasNumpy = packageNames.includes('numpy');
|
||||
const hasFastembed = packageNames.includes('fastembed');
|
||||
const hasHnswlib = packageNames.includes('hnswlib');
|
||||
|
||||
console.log(`[Test] numpy: ${hasNumpy}, fastembed: ${hasFastembed}, hnswlib: ${hasHnswlib}`);
|
||||
assert.ok(hasNumpy, 'numpy should be installed');
|
||||
assert.ok(hasFastembed, 'fastembed should be installed');
|
||||
assert.ok(hasHnswlib, 'hnswlib should be installed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should check individual package installation status', async () => {
|
||||
if (!uvAvailable || !manager?.isVenvValid()) {
|
||||
console.log('[Test] Skipping - venv not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
const numpyInstalled = await manager.isPackageInstalled('numpy');
|
||||
const fastembedInstalled = await manager.isPackageInstalled('fastembed');
|
||||
const nonexistentInstalled = await manager.isPackageInstalled('this-package-does-not-exist-12345');
|
||||
|
||||
console.log(`[Test] numpy installed: ${numpyInstalled}`);
|
||||
console.log(`[Test] fastembed installed: ${fastembedInstalled}`);
|
||||
console.log(`[Test] nonexistent installed: ${nonexistentInstalled}`);
|
||||
|
||||
assert.ok(numpyInstalled, 'numpy should be installed');
|
||||
assert.ok(fastembedInstalled, 'fastembed should be installed');
|
||||
assert.equal(nonexistentInstalled, false, 'nonexistent package should not be installed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens UV Manager Factory', () => {
|
||||
it('should create CodexLens UV manager with default settings', () => {
|
||||
const codexLensManager = mod.createCodexLensUvManager();
|
||||
console.log(`[Test] CodexLens manager created`);
|
||||
assert.ok(codexLensManager !== null, 'createCodexLensUvManager should return manager');
|
||||
assert.ok(codexLensManager.getVenvPython, 'Manager should have getVenvPython method');
|
||||
|
||||
// Verify Python path is in default location
|
||||
const pythonPath = codexLensManager.getVenvPython();
|
||||
console.log(`[Test] Default CodexLens Python path: ${pythonPath}`);
|
||||
assert.ok(pythonPath.includes('.codexlens'), 'Python path should be in .codexlens directory');
|
||||
});
|
||||
|
||||
it('should create CodexLens UV manager with custom data dir', () => {
|
||||
const customDir = join(tmpdir(), 'custom-codexlens-test');
|
||||
const codexLensManager = mod.createCodexLensUvManager(customDir);
|
||||
const pythonPath = codexLensManager.getVenvPython();
|
||||
console.log(`[Test] Custom CodexLens manager Python path: ${pythonPath}`);
|
||||
assert.ok(pythonPath.includes(customDir), 'Python path should use custom dir');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { after, afterEach, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { createRequire, syncBuiltinESMExports } from 'node:module';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('node:fs');
|
||||
|
||||
const originalExistsSync = fs.existsSync;
|
||||
const originalCodexLensDataDir = process.env.CODEXLENS_DATA_DIR;
|
||||
const tempDirs = [];
|
||||
|
||||
afterEach(() => {
|
||||
fs.existsSync = originalExistsSync;
|
||||
syncBuiltinESMExports();
|
||||
|
||||
if (originalCodexLensDataDir === undefined) {
|
||||
delete process.env.CODEXLENS_DATA_DIR;
|
||||
} else {
|
||||
process.env.CODEXLENS_DATA_DIR = originalCodexLensDataDir;
|
||||
}
|
||||
});
|
||||
|
||||
after(() => {
|
||||
while (tempDirs.length > 0) {
|
||||
rmSync(tempDirs.pop(), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('codexlens-path hidden python selection', () => {
|
||||
it('prefers pythonw.exe for hidden Windows subprocesses when available', async () => {
|
||||
if (process.platform !== 'win32') {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataDir = mkdtempSync(join(tmpdir(), 'ccw-codexlens-hidden-python-'));
|
||||
tempDirs.push(dataDir);
|
||||
process.env.CODEXLENS_DATA_DIR = dataDir;
|
||||
|
||||
const expectedPythonw = join(dataDir, 'venv', 'Scripts', 'pythonw.exe');
|
||||
fs.existsSync = (path) => String(path) === expectedPythonw;
|
||||
syncBuiltinESMExports();
|
||||
|
||||
const moduleUrl = new URL(`../dist/utils/codexlens-path.js?t=${Date.now()}`, import.meta.url);
|
||||
const mod = await import(moduleUrl.href);
|
||||
|
||||
assert.equal(mod.getCodexLensHiddenPython(), expectedPythonw);
|
||||
});
|
||||
|
||||
it('falls back to python.exe when pythonw.exe is unavailable', async () => {
|
||||
const dataDir = mkdtempSync(join(tmpdir(), 'ccw-codexlens-hidden-fallback-'));
|
||||
tempDirs.push(dataDir);
|
||||
process.env.CODEXLENS_DATA_DIR = dataDir;
|
||||
|
||||
fs.existsSync = () => false;
|
||||
syncBuiltinESMExports();
|
||||
|
||||
const moduleUrl = new URL(`../dist/utils/codexlens-path.js?t=${Date.now()}`, import.meta.url);
|
||||
const mod = await import(moduleUrl.href);
|
||||
|
||||
assert.equal(mod.getCodexLensHiddenPython(), mod.getCodexLensPython());
|
||||
});
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
import { afterEach, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const uvManagerPath = new URL('../dist/utils/uv-manager.js', import.meta.url).href;
|
||||
const pythonUtilsPath = new URL('../dist/utils/python-utils.js', import.meta.url).href;
|
||||
|
||||
describe('CodexLens UV python preference', async () => {
|
||||
let mod;
|
||||
let pythonUtils;
|
||||
const originalPython = process.env.CCW_PYTHON;
|
||||
|
||||
before(async () => {
|
||||
mod = await import(uvManagerPath);
|
||||
pythonUtils = await import(pythonUtilsPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalPython === undefined) {
|
||||
delete process.env.CCW_PYTHON;
|
||||
return;
|
||||
}
|
||||
process.env.CCW_PYTHON = originalPython;
|
||||
});
|
||||
|
||||
it('honors CCW_PYTHON override', () => {
|
||||
process.env.CCW_PYTHON = 'C:/Custom/Python/python.exe';
|
||||
assert.equal(mod.getPreferredCodexLensPythonSpec(), 'C:/Custom/Python/python.exe');
|
||||
});
|
||||
|
||||
it('parses py launcher commands into spawn-safe command specs', () => {
|
||||
const spec = pythonUtils.parsePythonCommandSpec('py -3.11');
|
||||
|
||||
assert.equal(spec.command, 'py');
|
||||
assert.deepEqual(spec.args, ['-3.11']);
|
||||
assert.equal(spec.display, 'py -3.11');
|
||||
});
|
||||
|
||||
it('treats unquoted Windows-style executable paths as a single command', () => {
|
||||
const spec = pythonUtils.parsePythonCommandSpec('C:/Program Files/Python311/python.exe');
|
||||
|
||||
assert.equal(spec.command, 'C:/Program Files/Python311/python.exe');
|
||||
assert.deepEqual(spec.args, []);
|
||||
assert.equal(spec.display, '"C:/Program Files/Python311/python.exe"');
|
||||
});
|
||||
|
||||
it('probes Python launcher versions without opening a shell window', () => {
|
||||
const probeCalls = [];
|
||||
const version = pythonUtils.probePythonCommandVersion(
|
||||
{ command: 'py', args: ['-3.11'], display: 'py -3.11' },
|
||||
(command, args, options) => {
|
||||
probeCalls.push({ command, args, options });
|
||||
return { status: 0, stdout: '', stderr: 'Python 3.11.9\n' };
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(version, 'Python 3.11.9');
|
||||
assert.equal(probeCalls.length, 1);
|
||||
assert.equal(probeCalls[0].command, 'py');
|
||||
assert.deepEqual(probeCalls[0].args, ['-3.11', '--version']);
|
||||
assert.equal(probeCalls[0].options.shell, false);
|
||||
assert.equal(probeCalls[0].options.windowsHide, true);
|
||||
assert.equal(probeCalls[0].options.env.PYTHONIOENCODING, 'utf-8');
|
||||
});
|
||||
|
||||
it('looks up uv on PATH without spawning a visible shell window', () => {
|
||||
const lookupCalls = [];
|
||||
const found = mod.__testables.findExecutableOnPath('uv', (command, args, options) => {
|
||||
lookupCalls.push({ command, args, options });
|
||||
return { status: 0, stdout: 'C:/Tools/uv.exe\n', stderr: '' };
|
||||
});
|
||||
|
||||
assert.equal(found, 'C:/Tools/uv.exe');
|
||||
assert.equal(lookupCalls.length, 1);
|
||||
assert.equal(lookupCalls[0].command, process.platform === 'win32' ? 'where' : 'which');
|
||||
assert.deepEqual(lookupCalls[0].args, ['uv']);
|
||||
assert.equal(lookupCalls[0].options.shell, false);
|
||||
assert.equal(lookupCalls[0].options.windowsHide, true);
|
||||
assert.equal(lookupCalls[0].options.env.PYTHONIOENCODING, 'utf-8');
|
||||
});
|
||||
|
||||
it('checks Windows launcher preferences with hidden subprocess options', () => {
|
||||
const probeCalls = [];
|
||||
const available = mod.__testables.hasWindowsPythonLauncherVersion('3.11', (command, args, options) => {
|
||||
probeCalls.push({ command, args, options });
|
||||
return { status: 0, stdout: '', stderr: 'Python 3.11.9\n' };
|
||||
});
|
||||
|
||||
assert.equal(available, true);
|
||||
assert.equal(probeCalls.length, 1);
|
||||
assert.equal(probeCalls[0].command, 'py');
|
||||
assert.deepEqual(probeCalls[0].args, ['-3.11', '--version']);
|
||||
assert.equal(probeCalls[0].options.shell, false);
|
||||
assert.equal(probeCalls[0].options.windowsHide, true);
|
||||
assert.equal(probeCalls[0].options.env.PYTHONIOENCODING, 'utf-8');
|
||||
});
|
||||
|
||||
it('prefers Python 3.11 or 3.10 on Windows when available', () => {
|
||||
if (process.platform !== 'win32') return;
|
||||
delete process.env.CCW_PYTHON;
|
||||
|
||||
let installed = '';
|
||||
try {
|
||||
installed = execSync('py -0p', { encoding: 'utf-8' });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const has311 = installed.includes('-V:3.11');
|
||||
const has310 = installed.includes('-V:3.10');
|
||||
if (!has311 && !has310) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preferred = mod.getPreferredCodexLensPythonSpec();
|
||||
assert.ok(
|
||||
preferred === '3.11' || preferred === '3.10',
|
||||
`expected Windows preference to avoid 3.12 when 3.11/3.10 exists, got ${preferred}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,414 +0,0 @@
|
||||
/**
|
||||
* Unit tests for uv-manager utility module.
|
||||
*
|
||||
* Notes:
|
||||
* - Targets the runtime implementation shipped in `ccw/dist`.
|
||||
* - Tests UV binary detection, installation, and virtual environment management.
|
||||
* - Gracefully handles cases where UV is not installed.
|
||||
*/
|
||||
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
const uvManagerUrl = new URL('../dist/utils/uv-manager.js', import.meta.url);
|
||||
uvManagerUrl.searchParams.set('t', String(Date.now()));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let mod: any;
|
||||
|
||||
// Test venv path with unique timestamp
|
||||
const TEST_VENV_PATH = join(tmpdir(), `uv-test-venv-${Date.now()}`);
|
||||
|
||||
// Track UV availability for conditional tests
|
||||
let uvAvailable = false;
|
||||
|
||||
describe('UV Manager Tests', async () => {
|
||||
mod = await import(uvManagerUrl.href);
|
||||
|
||||
// Cleanup after all tests
|
||||
after(() => {
|
||||
if (existsSync(TEST_VENV_PATH)) {
|
||||
console.log(`[Cleanup] Removing test venv: ${TEST_VENV_PATH}`);
|
||||
try {
|
||||
rmSync(TEST_VENV_PATH, { recursive: true, force: true });
|
||||
} catch (err) {
|
||||
console.log(`[Cleanup] Failed to remove venv: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('UV Binary Detection', () => {
|
||||
it('should check UV availability', async () => {
|
||||
uvAvailable = await mod.isUvAvailable();
|
||||
console.log(`[Test] UV available: ${uvAvailable}`);
|
||||
assert.equal(typeof uvAvailable, 'boolean', 'isUvAvailable should return boolean');
|
||||
});
|
||||
|
||||
it('should get UV version when available', async () => {
|
||||
if (uvAvailable) {
|
||||
const version = await mod.getUvVersion();
|
||||
console.log(`[Test] UV version: ${version}`);
|
||||
assert.ok(version !== null, 'getUvVersion should return version string');
|
||||
assert.ok(version.length > 0, 'Version string should not be empty');
|
||||
} else {
|
||||
console.log('[Test] UV not installed, skipping version test');
|
||||
const version = await mod.getUvVersion();
|
||||
assert.equal(version, null, 'getUvVersion should return null when UV not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('should get UV binary path', async () => {
|
||||
const path = mod.getUvBinaryPath();
|
||||
console.log(`[Test] UV path: ${path}`);
|
||||
assert.equal(typeof path, 'string', 'getUvBinaryPath should return string');
|
||||
assert.ok(path.length > 0, 'Path should not be empty');
|
||||
|
||||
if (uvAvailable) {
|
||||
assert.ok(existsSync(path), 'UV binary should exist when UV is available');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('UV Installation', () => {
|
||||
it('should ensure UV is installed', async () => {
|
||||
const installed = await mod.ensureUvInstalled();
|
||||
console.log(`[Test] UV ensured: ${installed}`);
|
||||
assert.equal(typeof installed, 'boolean', 'ensureUvInstalled should return boolean');
|
||||
|
||||
// Update availability after potential installation
|
||||
if (installed) {
|
||||
uvAvailable = await mod.isUvAvailable();
|
||||
assert.ok(uvAvailable, 'UV should be available after ensureUvInstalled returns true');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('UvManager Class', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let manager: any;
|
||||
|
||||
before(async () => {
|
||||
// Ensure UV is available for class tests
|
||||
if (!uvAvailable) {
|
||||
console.log('[Test] UV not available, attempting installation...');
|
||||
await mod.ensureUvInstalled();
|
||||
uvAvailable = await mod.isUvAvailable();
|
||||
}
|
||||
|
||||
manager = new mod.UvManager({
|
||||
venvPath: TEST_VENV_PATH,
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
console.log(`[Test] Created UvManager with venv path: ${TEST_VENV_PATH}`);
|
||||
});
|
||||
|
||||
it('should get venv Python path', () => {
|
||||
const pythonPath = manager.getVenvPython();
|
||||
console.log(`[Test] Venv Python path: ${pythonPath}`);
|
||||
assert.equal(typeof pythonPath, 'string', 'getVenvPython should return string');
|
||||
assert.ok(pythonPath.includes(TEST_VENV_PATH), 'Python path should be inside venv');
|
||||
});
|
||||
|
||||
it('should get venv pip path', () => {
|
||||
const pipPath = manager.getVenvPip();
|
||||
console.log(`[Test] Venv pip path: ${pipPath}`);
|
||||
assert.equal(typeof pipPath, 'string', 'getVenvPip should return string');
|
||||
assert.ok(pipPath.includes(TEST_VENV_PATH), 'Pip path should be inside venv');
|
||||
});
|
||||
|
||||
it('should report venv as invalid before creation', () => {
|
||||
const valid = manager.isVenvValid();
|
||||
console.log(`[Test] Venv valid (before create): ${valid}`);
|
||||
assert.equal(valid, false, 'Venv should not be valid before creation');
|
||||
});
|
||||
|
||||
it('should create virtual environment', async () => {
|
||||
if (!uvAvailable) {
|
||||
console.log('[Test] Skipping venv creation - UV not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.createVenv();
|
||||
console.log(`[Test] Create venv result:`, result);
|
||||
|
||||
if (result.success) {
|
||||
assert.ok(existsSync(TEST_VENV_PATH), 'Venv directory should exist');
|
||||
assert.ok(result.duration !== undefined, 'Duration should be reported');
|
||||
console.log(`[Test] Venv created in ${result.duration}ms`);
|
||||
} else {
|
||||
// May fail if Python is not installed
|
||||
console.log(`[Test] Venv creation failed: ${result.error}`);
|
||||
assert.equal(typeof result.error, 'string', 'Error should be a string');
|
||||
}
|
||||
});
|
||||
|
||||
it('should check if venv is valid after creation', () => {
|
||||
const valid = manager.isVenvValid();
|
||||
console.log(`[Test] Venv valid (after create): ${valid}`);
|
||||
assert.equal(typeof valid, 'boolean', 'isVenvValid should return boolean');
|
||||
});
|
||||
|
||||
it('should get Python version in venv', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping Python version check - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const version = await manager.getPythonVersion();
|
||||
console.log(`[Test] Python version: ${version}`);
|
||||
assert.ok(version !== null, 'getPythonVersion should return version');
|
||||
assert.ok(version.startsWith('3.'), 'Should be Python 3.x');
|
||||
});
|
||||
|
||||
it('should list installed packages', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping package list - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const packages = await manager.list();
|
||||
console.log(`[Test] Installed packages count: ${packages?.length ?? 0}`);
|
||||
|
||||
if (packages !== null) {
|
||||
assert.ok(Array.isArray(packages), 'list() should return array');
|
||||
// UV creates minimal venvs without pip by default
|
||||
console.log(`[Test] Packages in venv: ${packages.map((p: { name: string }) => p.name).join(', ') || '(empty)'}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check if package is installed', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping package check - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
// First install a package, then check if it's installed
|
||||
const installResult = await manager.install(['six']);
|
||||
if (installResult.success) {
|
||||
const installed = await manager.isPackageInstalled('six');
|
||||
console.log(`[Test] six installed: ${installed}`);
|
||||
assert.ok(installed, 'six should be installed after install');
|
||||
|
||||
// Clean up
|
||||
await manager.uninstall(['six']);
|
||||
} else {
|
||||
console.log('[Test] Could not install test package, skipping check');
|
||||
}
|
||||
});
|
||||
|
||||
it('should install a simple package', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping package install - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
// Install a small, fast-installing package
|
||||
const result = await manager.install(['pip-install-test']);
|
||||
console.log(`[Test] Install result:`, result);
|
||||
assert.equal(typeof result.success, 'boolean', 'success should be boolean');
|
||||
|
||||
if (result.success) {
|
||||
console.log(`[Test] Package installed in ${result.duration}ms`);
|
||||
} else {
|
||||
console.log(`[Test] Package install failed: ${result.error}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should uninstall a package', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping uninstall - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.uninstall(['pip-install-test']);
|
||||
console.log(`[Test] Uninstall result:`, result);
|
||||
assert.equal(typeof result.success, 'boolean', 'success should be boolean');
|
||||
|
||||
if (result.success) {
|
||||
console.log(`[Test] Package uninstalled in ${result.duration}ms`);
|
||||
} else {
|
||||
console.log(`[Test] Package uninstall failed: ${result.error}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty package list for install', async () => {
|
||||
const result = await manager.install([]);
|
||||
console.log(`[Test] Empty install result:`, result);
|
||||
assert.ok(result.success, 'Empty install should succeed');
|
||||
assert.equal(result.duration, 0, 'Empty install should have 0 duration');
|
||||
});
|
||||
|
||||
it('should handle empty package list for uninstall', async () => {
|
||||
const result = await manager.uninstall([]);
|
||||
console.log(`[Test] Empty uninstall result:`, result);
|
||||
assert.ok(result.success, 'Empty uninstall should succeed');
|
||||
assert.equal(result.duration, 0, 'Empty uninstall should have 0 duration');
|
||||
});
|
||||
|
||||
it('should run Python command in venv', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping Python command - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.runPython(['-c', 'print("hello from venv")']);
|
||||
console.log(`[Test] Run Python result:`, result);
|
||||
|
||||
if (result.success) {
|
||||
assert.ok(result.stdout.includes('hello from venv'), 'Output should contain expected text');
|
||||
} else {
|
||||
console.log(`[Test] Python command failed: ${result.stderr}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete virtual environment', async () => {
|
||||
if (!manager.isVenvValid()) {
|
||||
console.log('[Test] Skipping delete - venv not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await manager.deleteVenv();
|
||||
console.log(`[Test] Delete venv result: ${result}`);
|
||||
|
||||
if (result) {
|
||||
assert.ok(!existsSync(TEST_VENV_PATH), 'Venv directory should be deleted');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle deleteVenv when venv does not exist', async () => {
|
||||
const result = await manager.deleteVenv();
|
||||
console.log(`[Test] Delete non-existent venv result: ${result}`);
|
||||
assert.ok(result, 'Deleting non-existent venv should succeed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Helper Functions', () => {
|
||||
it('should create CodexLens UV manager with defaults', () => {
|
||||
const codexLensManager = mod.createCodexLensUvManager();
|
||||
console.log(`[Test] CodexLens manager created`);
|
||||
assert.ok(codexLensManager !== null, 'createCodexLensUvManager should return manager');
|
||||
assert.ok(codexLensManager.getVenvPython, 'Manager should have getVenvPython method');
|
||||
});
|
||||
|
||||
it('should create CodexLens UV manager with custom data dir', () => {
|
||||
const customDir = join(tmpdir(), 'custom-codexlens');
|
||||
const codexLensManager = mod.createCodexLensUvManager(customDir);
|
||||
const pythonPath = codexLensManager.getVenvPython();
|
||||
console.log(`[Test] Custom CodexLens manager Python path: ${pythonPath}`);
|
||||
assert.ok(pythonPath.includes(customDir), 'Python path should use custom dir');
|
||||
});
|
||||
|
||||
it('should bootstrap UV venv', async () => {
|
||||
if (!uvAvailable) {
|
||||
console.log('[Test] Skipping bootstrap - UV not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const bootstrapPath = join(tmpdir(), `uv-bootstrap-test-${Date.now()}`);
|
||||
console.log(`[Test] Bootstrap venv path: ${bootstrapPath}`);
|
||||
|
||||
try {
|
||||
const result = await mod.bootstrapUvVenv(bootstrapPath, '>=3.10');
|
||||
console.log(`[Test] Bootstrap result:`, result);
|
||||
assert.equal(typeof result.success, 'boolean', 'success should be boolean');
|
||||
|
||||
if (result.success) {
|
||||
assert.ok(existsSync(bootstrapPath), 'Bootstrap venv should exist');
|
||||
}
|
||||
} finally {
|
||||
// Cleanup bootstrap venv
|
||||
if (existsSync(bootstrapPath)) {
|
||||
rmSync(bootstrapPath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle install when UV not available gracefully', async () => {
|
||||
// Create manager pointing to non-existent venv
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const result = await badManager.install(['some-package']);
|
||||
console.log(`[Test] Install with invalid venv:`, result);
|
||||
assert.equal(result.success, false, 'Install should fail with invalid venv');
|
||||
assert.ok(result.error, 'Error message should be present');
|
||||
});
|
||||
|
||||
it('should handle uninstall when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const result = await badManager.uninstall(['some-package']);
|
||||
console.log(`[Test] Uninstall with invalid venv:`, result);
|
||||
assert.equal(result.success, false, 'Uninstall should fail with invalid venv');
|
||||
assert.ok(result.error, 'Error message should be present');
|
||||
});
|
||||
|
||||
it('should handle list when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const packages = await badManager.list();
|
||||
console.log(`[Test] List with invalid venv: ${packages}`);
|
||||
assert.equal(packages, null, 'list() should return null for invalid venv');
|
||||
});
|
||||
|
||||
it('should handle isPackageInstalled when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const installed = await badManager.isPackageInstalled('pip');
|
||||
console.log(`[Test] isPackageInstalled with invalid venv: ${installed}`);
|
||||
assert.equal(installed, false, 'isPackageInstalled should return false for invalid venv');
|
||||
});
|
||||
|
||||
it('should handle runPython when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const result = await badManager.runPython(['--version']);
|
||||
console.log(`[Test] runPython with invalid venv:`, result);
|
||||
assert.equal(result.success, false, 'runPython should fail for invalid venv');
|
||||
assert.ok(result.stderr.length > 0, 'Error message should be present');
|
||||
});
|
||||
|
||||
it('should handle sync when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const result = await badManager.sync('requirements.txt');
|
||||
console.log(`[Test] sync with invalid venv:`, result);
|
||||
assert.equal(result.success, false, 'sync should fail for invalid venv');
|
||||
assert.ok(result.error, 'Error message should be present');
|
||||
});
|
||||
|
||||
it('should handle installFromProject when venv not valid', async () => {
|
||||
const badManager = new mod.UvManager({
|
||||
venvPath: join(tmpdir(), 'non-existent-venv'),
|
||||
pythonVersion: '>=3.10',
|
||||
});
|
||||
|
||||
const result = await badManager.installFromProject('/some/project');
|
||||
console.log(`[Test] installFromProject with invalid venv:`, result);
|
||||
assert.equal(result.success, false, 'installFromProject should fail for invalid venv');
|
||||
assert.ok(result.error, 'Error message should be present');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user