// v1.13.5: truncate.ts unit coverage. Each test isolates TRUNCATION_DIR // under os.tmpdir() so concurrent vitest runs don't collide and the suite // stays self-cleaning. cleanupTruncations is covered by file-system half // only; the orphan-reap branch needs a real Postgres and is tested via the // smoke flow rather than vitest. import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; // Set the env var BEFORE importing the module so its module-load constant // reads the test directory rather than /tmp/boocode-truncations. const testDir = path.join(os.tmpdir(), `boocode-truncate-test-${process.pid}-${Date.now()}`); process.env.BOOCODE_TRUNCATION_DIR = testDir; const mod = await import('../truncate.js'); const { storeTruncation, readTruncation, truncateIfNeeded, MAX_TRUNCATION_BYTES } = mod; beforeAll(async () => { await fs.mkdir(testDir, { recursive: true }); }); afterEach(async () => { // Drop every file between tests so id-collision asserts and orphan-style // counts start from zero. const entries = await fs.readdir(testDir).catch(() => [] as string[]); await Promise.all(entries.map((n) => fs.unlink(path.join(testDir, n)).catch(() => {}))); }); describe('storeTruncation / readTruncation roundtrip', () => { it('writes and reads identical content', async () => { const original = 'hello\nworld\n' + 'x'.repeat(500); const id = await storeTruncation(original); expect(id).toMatch(/^tr_[0-9a-v]{12}$/); const got = await readTruncation(id); expect(got).toBe(original); }); it('readTruncation returns null for unknown ids', async () => { const got = await readTruncation('tr_000000000000'); expect(got).toBeNull(); }); it('readTruncation rejects malformed ids (returns null, never escapes dir)', async () => { // Path traversal attempt; readTruncation should not even try to open. const got = await readTruncation('../../etc/passwd'); expect(got).toBeNull(); }); }); describe('truncateIfNeeded', () => { it('returns sliced content with no outputPath when wasTruncated=false', async () => { const out = await truncateIfNeeded({ fullContent: 'irrelevant', slicedContent: 'visible', wasTruncated: false, }); expect(out).toEqual({ content: 'visible', truncated: false }); expect('outputPath' in out).toBe(false); }); it('stashes full content and returns outputPath when wasTruncated=true', async () => { const full = 'line1\nline2\nline3\nline4\n'; const sliced = 'line1\nline2\n[truncated]'; const out = await truncateIfNeeded({ fullContent: full, slicedContent: sliced, wasTruncated: true, }); expect(out.content).toBe(sliced); expect(out.truncated).toBe(true); expect(out.outputPath).toMatch(/^tr_[0-9a-v]{12}$/); const stashed = await readTruncation(out.outputPath!); expect(stashed).toBe(full); }); it('skips storage but still reports truncated when fullContent exceeds the cap', async () => { // Build content larger than MAX_TRUNCATION_BYTES. Use a Buffer to size // it without holding a literal that triggers the gigantic-string lint. const oversized = Buffer.alloc(MAX_TRUNCATION_BYTES + 1, 'x').toString('utf8'); const sliced = 'preview...'; const out = await truncateIfNeeded({ fullContent: oversized, slicedContent: sliced, wasTruncated: true, }); expect(out).toEqual({ content: sliced, truncated: true }); expect('outputPath' in out).toBe(false); }); it('storage failure surfaces as truncated without outputPath', async () => { // Force writeFile to throw. Spy at the fs module level since truncate.ts // imports { promises as fs } and storeTruncation calls fs.writeFile. const spy = vi.spyOn(fs, 'writeFile').mockRejectedValueOnce(new Error('disk full')); const out = await truncateIfNeeded({ fullContent: 'short', slicedContent: 'sliced', wasTruncated: true, }); expect(out).toEqual({ content: 'sliced', truncated: true }); expect('outputPath' in out).toBe(false); spy.mockRestore(); }); });