Merge pull request #3236 from deepseek-harness/fix/file-search-unreadable-subtree-test

test(file-reference-local): pin the unreadable-subtree test to POSIX non-root
This commit is contained in:
Chinesezjc
2026-08-27 22:48:31 +08:00
committed by GitHub
@@ -9,6 +9,24 @@ import {
WorkspaceFileSearch,
} from '../src/search.ts'
const fsControl = vi.hoisted(() => ({
/** Absolute path whose `readdir` rejects; the injectable stand-in for chmod 0. */
denyReaddir: undefined as string | undefined,
}))
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs/promises')>()
return {
...actual,
readdir: (async (path: unknown, ...rest: never[]) => {
if (fsControl.denyReaddir !== undefined && String(path) === fsControl.denyReaddir) {
throw Object.assign(new Error('EACCES: injected unreadable directory'), { code: 'EACCES' })
}
return (actual.readdir as (path: unknown, ...args: never[]) => Promise<unknown>)(path, ...rest)
}) as typeof actual.readdir,
}
})
const searches: WorkspaceFileSearch[] = []
const roots: string[] = []
/** Permission-stripped directories; restored before cleanup can remove them. */
@@ -199,7 +217,13 @@ describe('WorkspaceFileSearch', () => {
})
})
it('lets an unreadable subtree cost only its own candidates', async () => {
// chmod 0 can only deny directory reads on POSIX to a non-root owner:
// Windows exposes no directory permission bits for readdir, and root
// bypasses them. Where the fixture stays readable the sealed candidate is
// indexed, so the unreadable-branch behavior is pinned on POSIX non-root.
it.runIf(
process.getuid !== undefined && process.getuid() !== 0,
)('lets an unreadable subtree cost only its own candidates', async () => {
const root = await workspace()
const locked = join(root, 'locked')
await mkdir(locked, { recursive: true })
@@ -216,6 +240,29 @@ describe('WorkspaceFileSearch', () => {
expect(await files.list('locked', signal)).toEqual([{ path: 'locked', kind: 'directory' }])
})
// The chmod-0 fixture above cannot be built on Windows (no directory
// permission bits) or as root (bits are bypassed). An injected readdir
// failure keeps the unreadable-branch behavior covered on every platform.
it('lets an injected readdir failure cost only its own candidates', async () => {
const root = await workspace()
const locked = join(root, 'locked')
await mkdir(locked, { recursive: true })
await writeFile(join(locked, 'sealed.ts'), 'sealed')
fsControl.denyReaddir = locked
try {
const files = search(root)
const signal = new AbortController().signal
// The branch itself yields nothing, and the rest of the tree still does.
expect(await files.list('sealed', signal)).toEqual([])
expect(await files.list('README', signal)).toEqual([{ path: 'README.md', kind: 'file' }])
// The directory is still offered: only reading through it fails.
expect(await files.list('locked', signal)).toEqual([{ path: 'locked', kind: 'directory' }])
} finally {
fsControl.denyReaddir = undefined
}
})
it('enforces the entry cap', async () => {
const root = await workspace()
const capped = search(root, { maxEntries: 2 })