Files
gbrain/test/import-empty-code-file.test.ts
Garry TanandClaude Fable 5 8fa9bed815 review: empty-file skip must not enter the sync failure ledger; keep inline extract for non-brainDir sync targets
Two adversarial-review fixes on the #840/#1079 takeovers:

1. importCodeFile's empty-file skip returned skipped WITH an error string.
   performSync treats skipped-with-error as a parse failure (failedFiles →
   applySyncFailureGate), so any repo containing a legitimate empty code
   file (__init__.py, barrel stubs) would BLOCK last_commit for 3
   consecutive syncs and then live in the ledger as auto-skipped — the
   fix recreated #840's spurious-failure complaint one layer up, and the
   became-empty branch reported its own successful stale-page deletion as
   a failure. The skip now carries no error, matching the content-hash
   short-circuit (banked via markCompleted, never ledgered).

2. runPhaseSync passed noExtract: willRunExtractPhase to EVERY sync
   target, but the cycle's extract phase (runPhaseExtract →
   extractForSlugs) walks brainDir only — slugs synced from other
   sources' checkouts are invisible to it, so their link/timeline
   extraction was silently dropped forever. Inline extract is now
   deduped only for the brainDir target; other targets keep it, matching
   standalone 'gbrain sync --source X' behavior. Pinned by a new
   cycle.serial test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 11:07:49 -07:00

69 lines
2.9 KiB
TypeScript

/**
* Regression test for `importCodeFile` skipping empty (0-byte) code files (#840).
*
* Before this guard, an empty file produced a page row with empty
* compiled_truth, which then failed every subsequent `gbrain reindex-code`
* pass with `missing compiled_truth`. Empty files are legitimate in real
* repos (stub/placeholder files committed during refactors); the importer
* skips them the same way it skips unchanged content (benign, no error) — and when a
* previously-imported file BECOMES empty, the stale page is deleted (chunks
* cascade) instead of ghosting in search, mirroring the markdown importer's
* empty-content branch.
*/
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { importCodeFile } from '../src/core/import-file.ts';
let engine: PGLiteEngine;
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
});
afterAll(async () => {
await engine.disconnect();
});
describe('importCodeFile — empty file guard (#840)', () => {
test('zero-byte content is skipped, not inserted', async () => {
const result = await importCodeFile(engine, 'src/empty.ts', '', { noEmbed: true });
expect(result.status).toBe('skipped');
expect(result.chunks).toBe(0);
// NO error on the result: sync treats skipped-with-error as a parse
// failure and applySyncFailureGate would BLOCK last_commit on any repo
// containing an empty file. Empty files skip like the content-hash
// short-circuit — benign, banked, never ledgered.
expect(result.error).toBeUndefined();
// The skip must short-circuit before any page row is written, otherwise
// reindex-code will still trip on the missing compiled_truth.
const page = await engine.getPage(result.slug);
expect(page).toBeNull();
});
test('file that BECOMES empty deletes the stale page instead of ghosting', async () => {
const first = await importCodeFile(engine, 'src/becomes-empty.ts', 'export const x = 1;\n', { noEmbed: true });
expect(first.status).toBe('imported');
expect(await engine.getPage(first.slug)).not.toBeNull();
const second = await importCodeFile(engine, 'src/becomes-empty.ts', '', { noEmbed: true });
expect(second.status).toBe('skipped');
expect(second.error).toBeUndefined(); // benign skip — must not enter the sync failure ledger
// Stale page (and its chunks, via FK cascade) must be gone.
expect(await engine.getPage(first.slug)).toBeNull();
expect(await engine.getChunks(first.slug)).toEqual([]);
});
test('non-empty content still imports normally', async () => {
const result = await importCodeFile(engine, 'src/non-empty.ts', 'export const x = 1;\n', { noEmbed: true });
expect(result.status).toBe('imported');
expect(result.chunks).toBeGreaterThan(0);
});
});