import * as fs from 'fs';import * as os from 'os';import * as path from 'path';import { afterEach, describe, expect, it } from 'vitest'; import { ProjectPathResolver } from '../../../../src/main/services/discovery/ProjectPathResolver'; function createSessionLine(cwd: string): string { return JSON.stringify({ uuid: 'test-uuid', type: 'user', cwd, message: { role: 'user', content: 'hello' }, timestamp: new Date().toISOString(), });} describe('ProjectPathResolver', () => { const tempDirs: string[] = []; afterEach(async () => { // Allow Windows file handles from readline/streams to be released await new Promise((resolve) => setTimeout(resolve, 50)); for (const tempDir of tempDirs) { try { fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); } catch { // Ignore cleanup failures to prevent cascading test errors on Windows } } tempDirs.length = 0; }); it('prefers absolute cwd hint', async () => { const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); tempDirs.push(projectsDir); const resolver = new ProjectPathResolver(projectsDir); const resolved = await resolver.resolveProjectPath('-Users-test-proj', { cwdHint: '/Users/test/proj', }); expect(resolved).toBe('/Users/test/proj'); }); it('extracts cwd from session file when available', async () => { const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); tempDirs.push(projectsDir); const projectId = '-Users-test-my-repo'; const projectDir = path.join(projectsDir, projectId); fs.mkdirSync(projectDir, { recursive: true }); const sessionPath = path.join(projectDir, 'session-1.jsonl'); fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo')}\n`, 'utf8'); const resolver = new ProjectPathResolver(projectsDir); const resolved = await resolver.resolveProjectPath(projectId); expect(resolved).toBe('/Users/test/my-repo'); }); it('falls back to decoded project ID when no cwd is available', async () => { const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); tempDirs.push(projectsDir); const resolver = new ProjectPathResolver(projectsDir); const resolved = await resolver.resolveProjectPath('-C:-Users-test-my-repo'); expect(resolved).toBe('C:/Users/test/my/repo'); }); it('invalidates cached paths by project', async () => { const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); tempDirs.push(projectsDir); const projectId = '-Users-test-my-repo'; const projectDir = path.join(projectsDir, projectId); fs.mkdirSync(projectDir, { recursive: true }); const sessionPath = path.join(projectDir, 'session-1.jsonl'); fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v1')}\n`, 'utf8'); const resolver = new ProjectPathResolver(projectsDir); const firstResolved = await resolver.resolveProjectPath(projectId); expect(firstResolved).toBe('/Users/test/my-repo-v1'); fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v2')}\n`, 'utf8'); resolver.invalidateProject(projectId); const secondResolved = await resolver.resolveProjectPath(projectId); expect(secondResolved).toBe('/Users/test/my-repo-v2'); });});
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, describe, expect, it } from 'vitest';
import { ProjectPathResolver } from '../../../../src/main/services/discovery/ProjectPathResolver';
function createSessionLine(cwd: string): string {
return JSON.stringify({
uuid: 'test-uuid',
type: 'user',
cwd,
message: { role: 'user', content: 'hello' },
timestamp: new Date().toISOString(),
});
}
describe('ProjectPathResolver', () => {
const tempDirs: string[] = [];
afterEach(async () => {
// Allow Windows file handles from readline/streams to be released
await new Promise((resolve) => setTimeout(resolve, 50));
for (const tempDir of tempDirs) {
try {
fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
} catch {
// Ignore cleanup failures to prevent cascading test errors on Windows
}
}
tempDirs.length = 0;
});
it('prefers absolute cwd hint', async () => {
const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-'));
tempDirs.push(projectsDir);
const resolver = new ProjectPathResolver(projectsDir);
const resolved = await resolver.resolveProjectPath('-Users-test-proj', {
cwdHint: '/Users/test/proj',
});
expect(resolved).toBe('/Users/test/proj');
});
it('extracts cwd from session file when available', async () => {
const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-'));
tempDirs.push(projectsDir);
const projectId = '-Users-test-my-repo';
const projectDir = path.join(projectsDir, projectId);
fs.mkdirSync(projectDir, { recursive: true });
const sessionPath = path.join(projectDir, 'session-1.jsonl');
fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo')}\n`, 'utf8');
const resolver = new ProjectPathResolver(projectsDir);
const resolved = await resolver.resolveProjectPath(projectId);
expect(resolved).toBe('/Users/test/my-repo');
});
it('falls back to decoded project ID when no cwd is available', async () => {
const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-'));
tempDirs.push(projectsDir);
const resolver = new ProjectPathResolver(projectsDir);
const resolved = await resolver.resolveProjectPath('-C:-Users-test-my-repo');
expect(resolved).toBe('C:/Users/test/my/repo');
});
it('invalidates cached paths by project', async () => {
const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-'));
tempDirs.push(projectsDir);
const projectId = '-Users-test-my-repo';
const projectDir = path.join(projectsDir, projectId);
fs.mkdirSync(projectDir, { recursive: true });
const sessionPath = path.join(projectDir, 'session-1.jsonl');
fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v1')}\n`, 'utf8');
const resolver = new ProjectPathResolver(projectsDir);
const firstResolved = await resolver.resolveProjectPath(projectId);
expect(firstResolved).toBe('/Users/test/my-repo-v1');
fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v2')}\n`, 'utf8');
resolver.invalidateProject(projectId);
const secondResolved = await resolver.resolveProjectPath(projectId);
expect(secondResolved).toBe('/Users/test/my-repo-v2');
});
});