mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(markdown): keep readable documents after traversal errors (#27774)
* Keep readable Markdown documents after traversal errors * Document partial Markdown listing behavior --------- Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { basename, dirname, extname, join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -110,6 +110,38 @@ describe('Markdown listing with bundled ripgrep', () => {
|
||||
await expect(listMarkdownDocuments(root)).resolves.toEqual([])
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)(
|
||||
'returns readable Markdown when ignored database directories deny access',
|
||||
async () => {
|
||||
await put('README.md')
|
||||
await put('docs/guide.md')
|
||||
await put('.gitignore', '/data/\n')
|
||||
await put('data/clickhouse/store/hidden.md')
|
||||
const store = join(root, 'data/clickhouse/store')
|
||||
await chmod(store, 0o000)
|
||||
try {
|
||||
expect(
|
||||
(await listMarkdownDocuments(root)).map((document) => document.relativePath)
|
||||
).toEqual(['docs/guide.md', 'README.md'])
|
||||
} finally {
|
||||
await chmod(store, 0o700)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)(
|
||||
'still rejects an unreadable root',
|
||||
async () => {
|
||||
await put('README.md')
|
||||
await chmod(root, 0o000)
|
||||
try {
|
||||
await expect(listMarkdownDocuments(root)).rejects.toThrow()
|
||||
} finally {
|
||||
await chmod(root, 0o700)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it('rejects a missing root rather than treating it as an empty folder', async () => {
|
||||
await expect(listMarkdownDocuments(join(root, 'missing'))).rejects.toThrow()
|
||||
})
|
||||
|
||||
@@ -60,9 +60,10 @@ describe('Markdown document ripgrep lifecycle', () => {
|
||||
|
||||
it.each(['invalid', 'incomplete'] as const)('rejects %s UTF-8 filename bytes', async (kind) => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0')
|
||||
child.stdout.write(Buffer.from(kind === 'invalid' ? [0xff] : [0xe2, 0x82]))
|
||||
if (kind === 'incomplete') {
|
||||
child.emit('close', 0, null)
|
||||
child.emit('close', 2, null)
|
||||
}
|
||||
await expect(result).rejects.toThrow('not valid UTF-8')
|
||||
})
|
||||
@@ -90,18 +91,47 @@ describe('Markdown document ripgrep lifecycle', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('rejects an unreadable subtree even after receiving valid documents', async () => {
|
||||
it('returns readable documents when an unrelated subtree is unreadable', async () => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0')
|
||||
child.stderr.write('Permission denied')
|
||||
child.stdout.write('./docs/guide.md\0./README.md\0')
|
||||
child.stderr.write('rg: ./data/clickhouse/store: Permission denied (os error 13)')
|
||||
child.emit('close', 2, null)
|
||||
await expect(result).rejects.toThrow('Permission denied')
|
||||
expect((await result).map((document) => document.relativePath)).toEqual([
|
||||
'docs/guide.md',
|
||||
'README.md'
|
||||
])
|
||||
expect(child.kill).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rejects a truncated final path instead of returning partial documents', async () => {
|
||||
it.each(['', './script.ts\0./.md\0'])(
|
||||
'still rejects an unreadable listing without valid Markdown documents: %j',
|
||||
async (output) => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write(output)
|
||||
child.stderr.write('rg: .: Permission denied (os error 13)')
|
||||
child.emit('close', 2, null)
|
||||
await expect(result).rejects.toThrow('Permission denied')
|
||||
}
|
||||
)
|
||||
|
||||
it('still rejects unexpected exit codes after receiving valid documents', async () => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0')
|
||||
child.emit('close', 3, null)
|
||||
await expect(result).rejects.toThrow('Markdown document listing failed (3)')
|
||||
})
|
||||
|
||||
it('still rejects a signalled process after receiving valid documents', async () => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0')
|
||||
child.emit('close', 2, 'SIGTERM')
|
||||
await expect(result).rejects.toThrow('Markdown document listing failed (SIGTERM)')
|
||||
})
|
||||
|
||||
it.each([0, 2])('rejects a truncated final path on exit %s', async (code) => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0./unfinished.md')
|
||||
child.emit('close', 0, null)
|
||||
child.emit('close', code, null)
|
||||
await expect(result).rejects.toThrow('Incomplete path')
|
||||
})
|
||||
|
||||
@@ -109,6 +139,7 @@ describe('Markdown document ripgrep lifecycle', () => {
|
||||
'rejects a path outside the relative listing protocol: %s',
|
||||
async (path) => {
|
||||
const result = listMarkdownDocuments(root)
|
||||
child.stdout.write('./README.md\0')
|
||||
child.stdout.write(`${path}\0`)
|
||||
await expect(result).rejects.toThrow('Invalid path')
|
||||
expect(child.kill).toHaveBeenCalledWith('SIGKILL')
|
||||
@@ -215,7 +246,7 @@ it('rejects the 20,001st document without retaining the child', async () => {
|
||||
it('cancels the filtered producer and permits a fresh request', async () => {
|
||||
const controller = new AbortController()
|
||||
const result = listMarkdownDocuments(root, { signal: controller.signal })
|
||||
child.stdout.write('./partial')
|
||||
child.stdout.write('./README.md\0./partial')
|
||||
controller.abort(new Error('editor closed'))
|
||||
await expect(result).rejects.toThrow('editor closed')
|
||||
expect(child.kill).toHaveBeenCalledWith('SIGKILL')
|
||||
|
||||
@@ -23,6 +23,7 @@ export async function listMarkdownDocuments(
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
return collectMarkdownDocuments(child, rootPath, Boolean(distro), options.signal, {
|
||||
allowPartialListing: true,
|
||||
stopProcess: () => stopBundledRipgrep(child, Boolean(distro))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user