Files
wehub-resource-sync 7088f49b01
Build dist / build (push) Has been cancelled
CI / test (18.x) (push) Has been cancelled
CI / test (20.x) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:33 +08:00

176 lines
6.5 KiB
JavaScript

import { afterEach, test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { utimesSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
_getClaudeVersionInvocation,
_parseClaudeCodeVersion,
_resetVersionCache,
_setExecFileImplForTests,
_setResolveClaudeBinaryForTests,
_setVersionInvocationEnvForTests,
getClaudeCodeVersion,
} from '../dist/version.js';
function restoreEnvVar(name, value) {
if (value === undefined) {
delete process.env[name];
return;
}
process.env[name] = value;
}
afterEach(() => {
_resetVersionCache();
_setExecFileImplForTests(null);
_setResolveClaudeBinaryForTests(null);
_setVersionInvocationEnvForTests(null, null);
});
test('getClaudeCodeVersion returns undefined when no binary is resolved', async () => {
_setResolveClaudeBinaryForTests(() => null);
const version = await getClaudeCodeVersion();
assert.equal(version, undefined);
});
test('getClaudeCodeVersion returns undefined when execFile throws', async () => {
const tempHome = await mkdtemp(path.join(tmpdir(), 'claude-hud-ver-err-'));
const customConfigDir = path.join(tempHome, '.claude-alt');
const binaryPath = path.join(tempHome, 'claude');
const originalHome = process.env.HOME;
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.HOME = tempHome;
process.env.CLAUDE_CONFIG_DIR = customConfigDir;
await writeFile(binaryPath, '#!/bin/sh\n', 'utf8');
const binaryMtimeMs = 1710000000000;
utimesSync(binaryPath, binaryMtimeMs / 1000, binaryMtimeMs / 1000);
try {
_setResolveClaudeBinaryForTests(() => ({ path: binaryPath, mtimeMs: binaryMtimeMs }));
_setExecFileImplForTests(async () => {
throw new Error('command not found');
});
const version = await getClaudeCodeVersion();
assert.equal(version, undefined);
} finally {
restoreEnvVar('HOME', originalHome);
restoreEnvVar('CLAUDE_CONFIG_DIR', originalConfigDir);
await rm(tempHome, { recursive: true, force: true });
}
});
test('getClaudeCodeVersion uses in-memory cache on repeated calls without reset', async () => {
const tempHome = await mkdtemp(path.join(tmpdir(), 'claude-hud-ver-memcache-'));
const customConfigDir = path.join(tempHome, '.claude-alt');
const binaryPath = path.join(tempHome, 'claude');
const originalHome = process.env.HOME;
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
let execCalls = 0;
process.env.HOME = tempHome;
process.env.CLAUDE_CONFIG_DIR = customConfigDir;
await writeFile(binaryPath, '#!/bin/sh\n', 'utf8');
const binaryMtimeMs = 1710000000000;
utimesSync(binaryPath, binaryMtimeMs / 1000, binaryMtimeMs / 1000);
try {
_setResolveClaudeBinaryForTests(() => ({ path: binaryPath, mtimeMs: binaryMtimeMs }));
_setExecFileImplForTests(async () => {
execCalls += 1;
return { stdout: '2.1.90 (Claude Code)\n' };
});
const first = await getClaudeCodeVersion();
assert.equal(first, '2.1.90');
assert.equal(execCalls, 1);
// Second call without reset should use in-memory cache
const second = await getClaudeCodeVersion();
assert.equal(second, '2.1.90');
assert.equal(execCalls, 1); // no additional exec call
} finally {
restoreEnvVar('HOME', originalHome);
restoreEnvVar('CLAUDE_CONFIG_DIR', originalConfigDir);
await rm(tempHome, { recursive: true, force: true });
}
});
test('_parseClaudeCodeVersion handles various version output formats', () => {
assert.equal(_parseClaudeCodeVersion('1.0.0'), '1.0.0');
assert.equal(_parseClaudeCodeVersion(' \n '), undefined);
assert.equal(_parseClaudeCodeVersion('no version here'), undefined);
assert.equal(_parseClaudeCodeVersion('v2.3.4-rc.1+build.123'), '2.3.4-rc.1+build.123');
});
test('_getClaudeVersionInvocation handles .bat files on Windows via cmd', () => {
const invocation = _getClaudeVersionInvocation(
'C:\\Tools\\claude.bat',
'win32',
'C:\\Windows\\System32\\cmd.exe'
);
assert.equal(invocation.file, 'C:\\Windows\\System32\\cmd.exe');
// No special chars in the path, so it's not quoted
assert.deepEqual(invocation.args, ['/d', '/s', '/c', '"C:\\Tools\\claude.bat --version"']);
});
test('_getClaudeVersionInvocation handles paths with no special characters on Windows cmd', () => {
const invocation = _getClaudeVersionInvocation(
'C:\\claude.cmd',
'win32',
'C:\\Windows\\System32\\cmd.exe'
);
assert.equal(invocation.file, 'C:\\Windows\\System32\\cmd.exe');
assert.deepEqual(invocation.args, ['/d', '/s', '/c', '"C:\\claude.cmd --version"']);
});
test('_getClaudeVersionInvocation runs .exe directly on Windows', () => {
const invocation = _getClaudeVersionInvocation('C:\\path\\claude.exe', 'win32');
assert.deepEqual(invocation, { file: 'C:\\path\\claude.exe', args: ['--version'] });
});
test('_getClaudeVersionInvocation runs binary directly on darwin', () => {
const invocation = _getClaudeVersionInvocation('/opt/local/bin/claude', 'darwin');
assert.deepEqual(invocation, { file: '/opt/local/bin/claude', args: ['--version'] });
});
test('getClaudeCodeVersion handles disk cache with null version', async () => {
const tempHome = await mkdtemp(path.join(tmpdir(), 'claude-hud-ver-null-'));
const customConfigDir = path.join(tempHome, '.claude-alt');
const binaryPath = path.join(tempHome, 'claude');
const originalHome = process.env.HOME;
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.HOME = tempHome;
process.env.CLAUDE_CONFIG_DIR = customConfigDir;
await writeFile(binaryPath, '#!/bin/sh\n', 'utf8');
const binaryMtimeMs = 1710000000000;
utimesSync(binaryPath, binaryMtimeMs / 1000, binaryMtimeMs / 1000);
try {
// First call: exec returns unparseable output → null cached
_setResolveClaudeBinaryForTests(() => ({ path: binaryPath, mtimeMs: binaryMtimeMs }));
_setExecFileImplForTests(async () => ({ stdout: 'not a version\n' }));
const first = await getClaudeCodeVersion();
assert.equal(first, undefined);
// Reset and read from disk cache (null version should be preserved)
_resetVersionCache();
_setResolveClaudeBinaryForTests(() => ({ path: binaryPath, mtimeMs: binaryMtimeMs }));
_setExecFileImplForTests(async () => {
throw new Error('should not be called');
});
const second = await getClaudeCodeVersion();
assert.equal(second, undefined);
} finally {
restoreEnvVar('HOME', originalHome);
restoreEnvVar('CLAUDE_CONFIG_DIR', originalConfigDir);
await rm(tempHome, { recursive: true, force: true });
}
});