Files
fuck-u-code/tests/unit/git-utils.test.ts
Codex 80002aebea fix: harden security and clean up code quality issues
- git clone now uses execFile (no shell) and rejects URLs with shell metacharacters; removed dead parseRepoName.

- config show masks apiKey; config file chmod 0600 after writing keys.

- CLI launcher reports signal kills as failures and supports FUCK_U_CODE_MAX_MEMORY (default 4096MB).

- File discovery: well-known non-code dirs are defaults, not hard overrides; user-included dirs are analyzed.

- Nested .gitignore loading uses forward slashes so it works on Windows.

- Deduplicated analysis output rendering; fixed fetch timeout cleanup; aligned concurrency default to 8; enabled zh_TW locale end to end.

- Fixed pre-existing lint errors and Windows-flaky MCP tests; added git-utils, secrets, file-discovery tests; release workflow now runs tests.
2026-08-06 12:22:48 -07:00

91 lines
3.1 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { mkdtemp, mkdir, writeFile, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { gitClone, isValidGitUrl, removeTempDir } from '../../src/utils/git.js';
const execFileAsync = promisify(execFile);
describe('git utils', () => {
let tempRoot: string;
beforeEach(async () => {
tempRoot = await mkdtemp(join(tmpdir(), 'fuck-u-code-git-'));
});
afterEach(async () => {
await rm(tempRoot, { recursive: true, force: true });
});
describe('isValidGitUrl', () => {
it('accepts https, ssh and local paths', () => {
expect(isValidGitUrl('https://github.com/user/repo.git')).toBe(true);
expect(isValidGitUrl('git@github.com:user/repo.git')).toBe(true);
expect(isValidGitUrl('./local/repo')).toBe(true);
expect(isValidGitUrl('~/repo')).toBe(true);
});
it('rejects shell metacharacters and whitespace', () => {
expect(isValidGitUrl('https://example.com/repo;rm -rf /')).toBe(false);
expect(isValidGitUrl('https://example.com/repo$(id)')).toBe(false);
expect(isValidGitUrl('https://example.com/repo`id`')).toBe(false);
expect(isValidGitUrl('https://example.com/repo | cat')).toBe(false);
expect(isValidGitUrl('https://example.com/repo "quoted"')).toBe(false);
expect(isValidGitUrl('https://example.com/repo\n')).toBe(false);
});
});
describe('gitClone', () => {
it('clones a local repository', async () => {
const source = join(tempRoot, 'source');
await mkdir(source);
await writeFile(join(source, 'file.txt'), 'hello');
await execFileAsync('git', ['init', '-q', source]);
await execFileAsync('git', ['-C', source, 'add', '.']);
await execFileAsync('git', [
'-C',
source,
'-c',
'user.email=test@example.com',
'-c',
'user.name=test',
'commit',
'-qm',
'init',
]);
const target = join(tempRoot, 'clone');
const result = await gitClone(source, { targetDir: target, timeout: 30000 });
expect(result.success).toBe(true);
expect(result.targetDir).toBe(target);
await expect(stat(join(target, 'file.txt'))).resolves.toBeTruthy();
}, 30000);
it('never interprets a URL through a shell', async () => {
const marker = join(tempRoot, 'pwned');
const result = await gitClone(`./repo;echo pwned > ${marker}`, {
targetDir: join(tempRoot, 'out'),
timeout: 10000,
});
expect(result.success).toBe(false);
await expect(stat(marker)).rejects.toThrow();
});
});
describe('removeTempDir', () => {
it('removes an existing directory and tolerates a missing one', async () => {
const dir = join(tempRoot, 'temp-dir');
await mkdir(dir);
await writeFile(join(dir, 'x.txt'), 'x');
await expect(removeTempDir(dir)).resolves.toBe(true);
await expect(removeTempDir(join(tempRoot, 'missing'))).resolves.toBe(true);
});
});
});