import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' import { describe, expect, it } from 'vitest' import { findRepoSymbol, isRepoImagePath, pathInsideRoot, resolveRepoEntry } from './repoPath' describe('pathInsideRoot', () => { const root = '/repo' it('拼在仓库根里', () => { expect(pathInsideRoot(root, 'backend/src/a.py')).toBe('/repo/backend/src/a.py') expect(pathInsideRoot(root, 'vendor/agents')).toBe('/repo/vendor/agents') }) it('..、绝对路径、盘符不算', () => { expect(pathInsideRoot(root, '../etc/passwd')).toBeUndefined() expect(pathInsideRoot(root, 'foo/../../etc/passwd')).toBeUndefined() expect(pathInsideRoot(root, '/etc/passwd')).toBeUndefined() expect(pathInsideRoot(root, './x.md')).toBeUndefined() expect(pathInsideRoot(root, 'C:/foo')).toBeUndefined() }) }) describe('isRepoImagePath', () => { it('仓库里的图片算,跑出去的和不是图片的不算', () => { expect(isRepoImagePath('frontend/a.png')).toBe(true) expect(isRepoImagePath('frontend/a.PNG')).toBe(true) expect(isRepoImagePath('../a.png')).toBe(false) expect(isRepoImagePath('./a.png')).toBe(false) expect(isRepoImagePath('/a.png')).toBe(false) expect(isRepoImagePath('C:/a.png')).toBe(false) expect(isRepoImagePath('README.md')).toBe(false) }) }) describe('findRepoSymbol', () => { const tree = [ { name: 'Agent', children: [ { name: 'owner_id' }, { name: 'Nested', children: [{ name: 'inner' }] }, ], }, { name: 'load' }, ] it('按类名、函数名找', () => { expect(findRepoSymbol(tree, 'Agent')?.name).toBe('Agent') expect(findRepoSymbol(tree, 'load')?.name).toBe('load') }) it('类名.成员先找类,再在里面找成员', () => { expect(findRepoSymbol(tree, 'Agent.owner_id')?.name).toBe('owner_id') expect(findRepoSymbol(tree, 'Agent.inner')?.name).toBe('inner') expect(findRepoSymbol(tree, 'Agent.missing')).toBeUndefined() expect(findRepoSymbol(tree, 'Missing')).toBeUndefined() }) }) describe('resolveRepoEntry', () => { it('文件和目录在仓库里,跑到外面的符号链接不算', async () => { const root = await mkdtemp(path.join(tmpdir(), 'repo-link-')) const outside = await mkdtemp(path.join(tmpdir(), 'repo-link-out-')) try { await writeFile(path.join(root, 'ok.txt'), 'ok') await mkdir(path.join(root, 'dir')) await writeFile(path.join(outside, 'secret'), 'x') await symlink(path.join(outside, 'secret'), path.join(root, 'leak')) const file = await resolveRepoEntry(root, 'ok.txt') expect(file?.isDirectory).toBe(false) expect(file?.fsPath).toBe(await realpath(path.join(root, 'ok.txt'))) expect((await resolveRepoEntry(root, 'dir'))?.isDirectory).toBe(true) expect(await resolveRepoEntry(root, 'leak')).toBeUndefined() expect(await resolveRepoEntry(root, 'missing')).toBeUndefined() expect(await resolveRepoEntry(root, '../secret')).toBeUndefined() } finally { await rm(root, { recursive: true, force: true }) await rm(outside, { recursive: true, force: true }) } }) })