项目文件夹

文件
devin-ai-integration[bot] dd3f44718f Add unit tests for modules with lowest coverage (#635)
Add comprehensive tests targeting the 6 modules with the least test coverage:
- cost.ts: 87.64% → 100% statements, 75.6% → 100% branch
- identity.ts: 87.96% → 96.29% statements, 40.74% → 76.31% branch
- session-tokens.ts: 85.71% → 100% statements, 76.92% → 100% branch
- usage.ts: 85.95% → 97.94% statements, 82.29% → 88.88% branch
- memory.ts: 77.41% → 79.03% statements, 73.33% → 85.71% branch
- version.ts: 72.78% → 74.68% statements, 66.66% → 80.3% branch

Overall coverage improved from 93.56% → 94.6% statements, 84.54% → 86.23% branch.

Co-authored-by: Jarrod Watts <jarrodwatts16@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-19 11:37:41 -04:00

176 行
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 });
}
});