import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('node:child_process', () => ({
spawnSync: vi.fn(),
}));
vi.mock('../../src/cli/tui/detect-helpers.js', () => ({
binaryInPath: vi.fn(() => '/usr/bin/python3'),
}));
import { spawnSync } from 'node:child_process';
import {
checkVenvModule,
isMissingVenvModuleError,
venvInstallHint,
__resetResolvedPythonExe,
} from '../../src/python-env.js';
type SpawnResult = ReturnType<typeof spawnSync>;
function spawnResult(status: number | null, stdout = '', error?: Error): SpawnResult {
return { status, stdout, stderr: '', signal: null, pid: 1, output: [], error } as unknown as SpawnResult;
}
describe('checkVenvModule', () => {
beforeEach(() => { vi.clearAllMocks(); __resetResolvedPythonExe(); });
it('reports available + version when ensurepip and venv import cleanly', () => {
vi.mocked(spawnSync).mockImplementation((_cmd, args) => {
const a = (args ?? []) as string[];
if (a.join(' ').includes('version_info')) return spawnResult(0, '3.12\n');
return spawnResult(0);
});
const r = checkVenvModule('python3');
expect(r.available).toBe(true);
expect(r.pythonVersion).toBe('3.12');
});
it('reports unavailable when the ensurepip/venv import fails (Debian symptom)', () => {
vi.mocked(spawnSync).mockImplementation((_cmd, args) => {
const a = (args ?? []) as string[];
if (a.join(' ').includes('version_info')) return spawnResult(0, '3.11\n');
return spawnResult(1);
});
const r = checkVenvModule('python3');
expect(r.available).toBe(false);
expect(r.pythonVersion).toBe('3.11');
});
it('treats an unrunnable interpreter as "available" so the real error surfaces', () => {
vi.mocked(spawnSync).mockReturnValue(spawnResult(null, '', new Error('ENOENT')));
const r = checkVenvModule('python3');
expect(r.available).toBe(true);
expect(r.pythonVersion).toBeUndefined();
});
});
describe('isMissingVenvModuleError', () => {
it('recognizes the ensurepip stderr signature', () => {
expect(isMissingVenvModuleError(
'Error: Command ... -m ensurepip ... returned non-zero exit status 1.',
)).toBe(true);
});
it('recognizes the "No module named venv" signature', () => {
expect(isMissingVenvModuleError("ModuleNotFoundError: No module named 'venv'")).toBe(true);
});
it('recognizes the apt-package mention', () => {
expect(isMissingVenvModuleError('You may need to install the python3-venv package')).toBe(true);
});
it('does NOT misfire on an unrelated pip failure', () => {
expect(isMissingVenvModuleError('ERROR: Could not find a version that satisfies requirement')).toBe(false);
});
it('returns false for empty stderr', () => {
expect(isMissingVenvModuleError('')).toBe(false);
});
});
describe('venvInstallHint', () => {
it('names the exact versioned package when version is known', () => {
const hint = venvInstallHint('3.12');
expect(hint).toContain('sudo apt install python3.12-venv');
expect(hint).toContain('python3-venv');
expect(hint).toContain('core backend');
});
it('falls back to generic python3-venv when version unknown', () => {
const hint = venvInstallHint();
expect(hint).toContain('sudo apt install python3-venv');
expect(hint).not.toContain('python3.');
});
});