Files
goodbuddy/src/main/agent/paged-output-store.test.ts
T

105 lines
4.5 KiB
TypeScript

import { mkdtemp, readdir, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { PagedOutputStore, PAGED_OUTPUT_MAX_PAGE_BYTES } from './paged-output-store'
const stores: PagedOutputStore[] = []
const directories: string[] = []
afterEach(async () => {
await Promise.all(stores.splice(0).map((store) => store.dispose()))
await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })))
})
async function createStore() {
const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-output-test-'))
directories.push(directory)
const store = new PagedOutputStore('test', { temporaryParentDirectory: directory })
stores.push(store)
return { store, directory }
}
describe('PagedOutputStore', () => {
it.each([1, 2, 3, 4, 7])('round trips split UTF-8 chunks with a %i byte page', async (limit) => {
const { store } = await createStore()
const text = 'a\u4f60\ud83d\ude00\ufffd\u597dZ'
const writer = await store.create('owner', 2)
for (const byte of Buffer.from(text)) writer.append(Buffer.from([byte]))
const saved = await writer.finish()
expect(saved.text).toBe('a')
let output = saved.text
let cursor = saved.reference!.nextCursor
while (cursor < saved.reference!.totalBytes) {
const page = await store.read('owner', writer.handle, cursor, limit)
expect(page.nextCursor).toBeGreaterThan(cursor)
expect(Buffer.byteLength(page.content)).toBeLessThanOrEqual(Math.max(limit, 4))
output += page.content
cursor = page.nextCursor
}
expect(output).toBe(text)
await expect(store.read('owner', writer.handle, cursor, limit)).resolves.toMatchObject({ content: '', eof: true, nextCursor: cursor })
await expect(store.read('owner', writer.handle, 2)).rejects.toThrow('UTF-8')
await expect(store.read('other', writer.handle)).rejects.toThrow()
for (const invalid of [-1, 0.5, cursor + 1]) {
await expect(store.read('owner', writer.handle, invalid)).rejects.toThrow()
}
for (const invalid of [0, 0.5, PAGED_OUTPUT_MAX_PAGE_BYTES + 1]) {
await expect(store.read('owner', writer.handle, 0, invalid)).rejects.toThrow()
}
})
it('keeps literal replacement characters in previews', async () => {
const { store } = await createStore()
const writer = await store.create('owner', 3)
writer.append('\ufffdrest')
const saved = await writer.finish()
expect(saved.text).toBe('\ufffd')
expect(saved.reference?.nextCursor).toBe(3)
})
it('retains output omitted to keep JSON-escaped previews bounded', async () => {
const { store } = await createStore()
const writer = await store.create('owner', 12)
writer.append('\u0000'.repeat(12))
const saved = await writer.finish()
expect(saved.text).toBe('\u0000\u0000')
expect(saved.reference?.nextCursor).toBe(2)
const page = await store.read('owner', writer.handle, saved.reference!.nextCursor)
expect(saved.text + page.content).toBe('\u0000'.repeat(12))
})
it('removes small, aborted, released and disposed output files', async () => {
const { store, directory } = await createStore()
const small = await store.create('owner', 10)
small.append('small')
expect(await small.finish()).toEqual({ text: 'small', truncated: false })
const retained = await store.create('owner', 1)
retained.append('retained')
await retained.finish()
const active = await store.create('owner', 1)
active.append('active')
const other = await store.create('other', 1)
other.append('other')
await other.finish()
const [childDirectory] = await readdir(directory)
await store.releaseOwner('owner')
expect(await readdir(join(directory, childDirectory!))).toHaveLength(1)
await expect(active.finish()).rejects.toThrow()
await expect(store.read('owner', retained.handle)).rejects.toThrow()
await expect(store.read('other', other.handle)).resolves.toMatchObject({ content: 'other' })
await store.create('other', 1)
await store.dispose()
expect(await readdir(directory)).toEqual([])
await expect(store.create('owner', 1)).rejects.toThrow()
})
it('cleans up when disposed during directory creation', async () => {
const { store, directory } = await createStore()
const pending = store.create('owner', 1)
const rejected = expect(pending).rejects.toThrow()
await store.dispose()
await rejected
expect(await readdir(directory)).toEqual([])
})
})