113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
import { Buffer } from 'node:buffer'
|
|
import { randomBytes } from 'node:crypto'
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import * as os from 'node:os'
|
|
import * as path from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { formatByteSize, HANDOFF_ATTACHMENT_MAX_BYTES, HandoffArchiveTooLargeError, packHandoff } from './handoffArchive'
|
|
import { ensureHandoffFits, HandoffSourcesTooLargeError, measureHandoffSources } from './handoffSize'
|
|
|
|
const tempDirs: string[] = []
|
|
afterEach(() => {
|
|
while (tempDirs.length > 0) {
|
|
const dir = tempDirs.pop()
|
|
if (dir)
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
function tempDir(prefix: string): string {
|
|
const dir = mkdtempSync(path.join(os.tmpdir(), prefix))
|
|
tempDirs.push(dir)
|
|
return dir
|
|
}
|
|
|
|
const BRAIN = 'aaaaaaaa-0000-4000-8000-000000000001'
|
|
const IMPL = 'bbbbbbbb-0000-4000-8000-000000000002'
|
|
const REVIEW = '019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a'
|
|
|
|
function fixture(implBytes: Buffer): { projectsRoot: string, codexRoot: string } {
|
|
const projectsRoot = tempDir('handoff-size-projects-')
|
|
const codexRoot = tempDir('handoff-size-codex-')
|
|
const proj = path.join(projectsRoot, '-Users-chw-proj')
|
|
const wt = path.join(projectsRoot, '-Users-chw-wt-x')
|
|
mkdirSync(proj, { recursive: true })
|
|
mkdirSync(path.join(wt, IMPL, 'subagents'), { recursive: true })
|
|
writeFileSync(path.join(proj, `${BRAIN}.jsonl`), 'brain\n')
|
|
writeFileSync(path.join(wt, `${IMPL}.jsonl`), implBytes)
|
|
writeFileSync(path.join(wt, IMPL, 'subagents', 'agent-1.jsonl'), '{}\n')
|
|
const day = path.join(codexRoot, '2026', '08', '26')
|
|
mkdirSync(day, { recursive: true })
|
|
writeFileSync(path.join(day, `rollout-2026-08-26T01-02-03-${REVIEW}.jsonl`), 'review\n')
|
|
return { projectsRoot, codexRoot }
|
|
}
|
|
|
|
function source(roots: { projectsRoot: string, codexRoot: string }) {
|
|
return {
|
|
src: {
|
|
issueNumber: 448,
|
|
from: 'chw',
|
|
sessions: {
|
|
sessionId: BRAIN,
|
|
implementSessionId: IMPL,
|
|
testSessionId: 'missing-0000-4000-8000-000000000000',
|
|
reviewSessionId: REVIEW,
|
|
},
|
|
profiles: {},
|
|
},
|
|
roots,
|
|
}
|
|
}
|
|
|
|
describe('formatByteSize', () => {
|
|
it('100MB 上限按整数 MB 显示', () => {
|
|
expect(formatByteSize(HANDOFF_ATTACHMENT_MAX_BYTES)).toBe('100MB')
|
|
})
|
|
})
|
|
|
|
describe('measureHandoffSources', () => {
|
|
it('jsonl、会话目录、codex 加在一起,缺失的会话不出现', async () => {
|
|
const roots = fixture(Buffer.from('impl\n'))
|
|
const { src } = source(roots)
|
|
const items = await measureHandoffSources(src, roots)
|
|
expect(items).toEqual([
|
|
{ role: '头脑风暴', bytes: Buffer.byteLength('brain\n') },
|
|
{ role: '实施', bytes: Buffer.byteLength('impl\n') },
|
|
{ role: '审查', bytes: Buffer.byteLength('review\n') },
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('ensureHandoffFits', () => {
|
|
it('原始大小不超过上限时直接通过', async () => {
|
|
const roots = fixture(Buffer.from('impl\n'))
|
|
const { src } = source(roots)
|
|
const items = await ensureHandoffFits(src, roots, { maxBytes: 1024 * 1024 })
|
|
expect(items.map(item => item.role)).toEqual(['头脑风暴', '实施', '审查'])
|
|
})
|
|
|
|
it('压缩后超过上限就停,并说明还没开始移交', async () => {
|
|
const roots = fixture(randomBytes(80 * 1024))
|
|
const { src } = source(roots)
|
|
await expect(ensureHandoffFits(src, roots, { maxBytes: 1024 })).rejects.toBeInstanceOf(HandoffSourcesTooLargeError)
|
|
try {
|
|
await ensureHandoffFits(src, roots, { maxBytes: 1024 })
|
|
}
|
|
catch (err) {
|
|
expect(err).toBeInstanceOf(HandoffSourcesTooLargeError)
|
|
expect((err as Error).message).toContain('没有开始移交')
|
|
expect((err as Error).message).toContain('实施')
|
|
expect((err as Error).message).toContain('1.0KB')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('packHandoff', () => {
|
|
it('产出超过 maxBytes 就失败,不等到打完', async () => {
|
|
const staging = tempDir('handoff-pack-')
|
|
writeFileSync(path.join(staging, 'big.bin'), randomBytes(80 * 1024))
|
|
const out = path.join(tempDir('handoff-pack-out-'), 'bundle.tgz')
|
|
await expect(packHandoff(staging, out, { maxBytes: 1024 })).rejects.toBeInstanceOf(HandoffArchiveTooLargeError)
|
|
})
|
|
})
|