feat(fs): add directory listing seam
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { mkdtemp, readFile, rm, stat, symlink, writeFile, unlink } from 'node:fs/promises'
|
||||
import { mkdir, mkdtemp, readFile, realpath, rm, stat, symlink, writeFile, unlink } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { Context } from 'cordis'
|
||||
@@ -118,6 +118,50 @@ describe('readText / streamText', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('listDir', () => {
|
||||
it('lists files and directories in stable name order with resolved child targets', async () => {
|
||||
await mkdir(join(dir, 'skills', 'dir-skill'), { recursive: true })
|
||||
await writeFile(join(dir, 'skills', 'zeta.md'), 'zeta')
|
||||
await writeFile(join(dir, 'skills', 'alpha.md'), 'alpha')
|
||||
await symlink(join(dir, 'skills', 'missing-target'), join(dir, 'skills', 'broken-link'))
|
||||
|
||||
const entries = await fs.listDir(await fs.resolve('skills'))
|
||||
expect(entries.map(entry => [entry.name, entry.type])).toEqual([
|
||||
['alpha.md', 'file'],
|
||||
['broken-link', 'other'],
|
||||
['dir-skill', 'directory'],
|
||||
['zeta.md', 'file'],
|
||||
])
|
||||
expect(entries.map(entry => entry.target.displayPath)).toEqual([
|
||||
join(dir, 'skills', 'alpha.md'),
|
||||
join(dir, 'skills', 'broken-link'),
|
||||
join(dir, 'skills', 'dir-skill'),
|
||||
join(dir, 'skills', 'zeta.md'),
|
||||
])
|
||||
const materializedEntries = entries.filter(entry => entry.version !== undefined)
|
||||
expect(materializedEntries.map(entry => entry.target.targetKey))
|
||||
.toEqual(await Promise.all(materializedEntries.map(entry => realpath(entry.target.displayPath))))
|
||||
expect(entries.find(entry => entry.name === 'alpha.md')?.size).toBe(5)
|
||||
expect(typeof entries.find(entry => entry.name === 'alpha.md')?.version).toBe('string')
|
||||
expect(entries.find(entry => entry.name === 'broken-link')?.version).toBeUndefined()
|
||||
expect(entries.find(entry => entry.name === 'dir-skill')?.size).toBeUndefined()
|
||||
})
|
||||
|
||||
it('reports a missing directory as FS_NOT_FOUND', async () => {
|
||||
await expect(fs.listDir(await fs.resolve('missing'))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
|
||||
})
|
||||
|
||||
it('reports a file target as FS_NOT_DIRECTORY', async () => {
|
||||
await writeFile(join(dir, 'a.txt'), 'text')
|
||||
await expect(fs.listDir(await fs.resolve('a.txt'))).rejects.toMatchObject({ code: 'FS_NOT_DIRECTORY' })
|
||||
})
|
||||
|
||||
it('honors a pre-aborted signal', async () => {
|
||||
await mkdir(join(dir, 'skills'), { recursive: true })
|
||||
await expect(fs.listDir(await fs.resolve('skills'), AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('writeText', () => {
|
||||
it('createIfAbsent creates a new file', async () => {
|
||||
const target = await fs.resolve('new.txt')
|
||||
|
||||
@@ -12,6 +12,7 @@ import { join } from 'node:path'
|
||||
import { createServer } from 'node:net'
|
||||
import {
|
||||
applyLiteralEdit,
|
||||
listDirectory,
|
||||
probe,
|
||||
readForEdit,
|
||||
readWholeText,
|
||||
@@ -145,6 +146,36 @@ describe('probe', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('listDirectory', () => {
|
||||
it('lists direct children in stable order without reading content', async () => {
|
||||
const root = join(dir, 'skills')
|
||||
await mkdir(join(root, 'dir-skill'), { recursive: true })
|
||||
await writeFile(join(root, 'zeta.md'), 'zeta')
|
||||
await writeFile(join(root, 'alpha.md'), 'alpha')
|
||||
await symlink(join(root, 'missing-target'), join(root, 'broken-link'))
|
||||
|
||||
const entries = await listDirectory(localTarget(root))
|
||||
expect(entries.map(entry => [entry.name, entry.type])).toEqual([
|
||||
['alpha.md', 'file'],
|
||||
['broken-link', 'other'],
|
||||
['dir-skill', 'directory'],
|
||||
['zeta.md', 'file'],
|
||||
])
|
||||
expect(entries.find(entry => entry.name === 'alpha.md')?.size).toBe(5)
|
||||
expect(typeof entries.find(entry => entry.name === 'alpha.md')?.version).toBe('string')
|
||||
expect(entries.find(entry => entry.name === 'broken-link')?.version).toBeUndefined()
|
||||
expect(entries.find(entry => entry.name === 'dir-skill')?.size).toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects missing, non-directory, and aborted listing requests', async () => {
|
||||
await expect(listDirectory(localTarget(join(dir, 'missing')))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
|
||||
const file = join(dir, 'a.txt')
|
||||
await writeFile(file, 'hi')
|
||||
await expect(listDirectory(localTarget(file))).rejects.toMatchObject({ code: 'FS_NOT_DIRECTORY' })
|
||||
await expect(listDirectory(localTarget(dir), AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('readWholeText', () => {
|
||||
it('reads a small file', async () => {
|
||||
const file = join(dir, 'a.txt')
|
||||
|
||||
Reference in New Issue
Block a user