Files
gbrain/test/code-intel/traversal-cache.test.ts
T
Garry TanandClaude Opus 4.7 f25b674fbf Merge origin/master into garrytan/managua-v3
Resolves v0.34.0.0 (W1-W8 code intelligence) with master's v0.33.2.1 +
search-lite work (query cache + intent weighting + token budget + drift
watch + metric glossary + search modes).

Conflict resolutions:
- VERSION / package.json: kept 0.34.0.0 (mine; higher than master's 0.33.2.1)
- CHANGELOG.md: both entries preserved; reordered so v0.33.2.1 sits above
  v0.33.2.0 (semver order)
- src/cli.ts CLI_ONLY: union of both — `edges-backfill` (mine) + `cache`
  (master)
- src/core/migrate.ts: renumbered my migrations to avoid collision with
  master's query_cache_search_lite (v55), query_cache_knobs_hash (v56),
  search_telemetry_rollup (v57). My `edges_backfilled_at_v0_33_2` moves
  v55 → v58; my `code_traversal_cache_v0_34` moves v56 → v59. Code refs
  in `src/core/code-intel/traversal-cache.ts` and the paired test
  updated to match.
- src/core/operations.ts query op: kept master's `hybridSearchCached`
  routing (search-lite cache integration) AND my `sourceId` resolution
  block (D4 source-routing fix from v0.34 STEP 0). Both apply.

Verification:
- `bun run typecheck` clean
- `bun run verify` clean (includes check-cli-executable, check-jsonb,
  check-system-of-record, check-eval-glossary-fresh, etc.)
- Migration v50→v59 apply cleanly on PGLite in isolated test runs
- Individual test files pass (e.g. test/search-lang-symbol-kind.test.ts:
  9 pass / 0 fail in 913ms)

Known follow-up: the parallel test shard runner times out some
beforeAll hooks at the default 7s budget. Tests pass when run
sequentially (`--max-concurrency=1`); 27/0 confirmed across 3 sample
files in 2.4s sequential vs timeouts under parallel-shard contention.
Master added 4 new migrations (v55-v57 + search-lite related)
increasing per-test-file PGLite init cost; on 8 shards racing for OS
resources, some shards hit the 7s ceiling. This is a test-infrastructure
issue (shard isolation under heavier migrations), not a code-correctness
issue. Fix is a follow-up: either raise shard test timeout, reduce
shard count, or migrate to fixture-based engine setup for hot tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:38:00 -07:00

184 lines
6.1 KiB
TypeScript

/**
* v0.34 W3b — code_traversal_cache module tests.
*
* Hermetic PGLite test suite covering:
* - cache hit returns memoized response (after migration v59)
* - cache miss triggers compute
* - D3: cluster_generation bump invalidates cached rows
* - clearTraversalCache: source-scoped clear deletes the right rows
* - clearTraversalCache: --all-sources gate requires explicit opt-out
* - getCachedOrCompute: try-cache-then-compute happy path
*/
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
import { PGLiteEngine } from '../../src/core/pglite-engine.ts';
import { resetPgliteState } from '../helpers/reset-pglite.ts';
import {
getCachedTraversal,
putCachedTraversal,
getClusterGeneration,
bumpClusterGeneration,
clearTraversalCache,
getCachedOrCompute,
type CacheKey,
} from '../../src/core/code-intel/traversal-cache.ts';
let engine: PGLiteEngine;
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
});
afterAll(async () => {
await engine.disconnect();
});
beforeEach(async () => {
await resetPgliteState(engine);
});
const baseKey = (over: Partial<CacheKey> = {}): CacheKey => ({
symbol_qualified: 'src/foo::bar',
depth: 5,
source_id: 'default',
cluster_generation: 0,
...over,
});
describe('W3b: getClusterGeneration / bumpClusterGeneration', () => {
test('defaults to 0 when never set', async () => {
const g = await getClusterGeneration(engine);
expect(g).toBe(0);
});
test('bump increments by 1 and persists', async () => {
const next = await bumpClusterGeneration(engine);
expect(next).toBe(1);
const read = await getClusterGeneration(engine);
expect(read).toBe(1);
});
test('multiple bumps are monotonic', async () => {
await bumpClusterGeneration(engine);
await bumpClusterGeneration(engine);
const final = await bumpClusterGeneration(engine);
expect(final).toBe(3);
});
});
describe('W3b: putCachedTraversal / getCachedTraversal', () => {
test('cache miss returns null', async () => {
const hit = await getCachedTraversal(engine, baseKey());
expect(hit).toBeNull();
});
test('cache hit returns the response after put', async () => {
const key = baseKey();
const payload = { result: 'ok', depth_groups: [{ depth: 1, nodes: [] }] };
await putCachedTraversal(engine, key, payload, new Date().toISOString(), 0);
const hit = await getCachedTraversal(engine, key);
expect(hit).not.toBeNull();
expect(hit?.response).toEqual(payload);
});
test('D3: cluster_generation mismatch returns null (cache miss)', async () => {
const key = baseKey({ cluster_generation: 1 });
await putCachedTraversal(engine, key, { v: 'fresh' }, new Date().toISOString(), 0);
// Now look up with a stale generation
const staleKey = baseKey({ cluster_generation: 0 });
const hit = await getCachedTraversal(engine, staleKey);
expect(hit).toBeNull();
});
test('UPSERT on conflict replaces older row', async () => {
const key = baseKey();
await putCachedTraversal(engine, key, { v: 1 }, new Date().toISOString(), 0);
await putCachedTraversal(engine, key, { v: 2 }, new Date().toISOString(), 0);
const hit = await getCachedTraversal<{ v: number }>(engine, key);
expect(hit?.response).toEqual({ v: 2 });
});
});
describe('W3b: clearTraversalCache', () => {
test('refuses without source_id or all_sources', async () => {
await expect(clearTraversalCache(engine, {})).rejects.toThrow(/specify source_id/);
});
test('source-scoped clear deletes only that source', async () => {
await putCachedTraversal(engine, baseKey({ source_id: 'src-a' }), { v: 1 }, new Date().toISOString(), 0);
await putCachedTraversal(engine, baseKey({ source_id: 'src-b' }), { v: 2 }, new Date().toISOString(), 0);
const deleted = await clearTraversalCache(engine, { sourceId: 'src-a' });
expect(deleted).toBe(1);
const a = await getCachedTraversal(engine, baseKey({ source_id: 'src-a' }));
const b = await getCachedTraversal(engine, baseKey({ source_id: 'src-b' }));
expect(a).toBeNull();
expect(b).not.toBeNull();
});
test('all_sources clears everything', async () => {
await putCachedTraversal(engine, baseKey({ source_id: 'src-a' }), { v: 1 }, new Date().toISOString(), 0);
await putCachedTraversal(engine, baseKey({ source_id: 'src-b' }), { v: 2 }, new Date().toISOString(), 0);
const deleted = await clearTraversalCache(engine, { allSources: true });
expect(deleted).toBe(2);
});
});
describe('W3b: getCachedOrCompute', () => {
test('miss: runs compute and caches result', async () => {
let computeCalls = 0;
const result = await getCachedOrCompute(
engine,
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
async () => {
computeCalls += 1;
return { x: 42 };
},
);
expect(result).toEqual({ x: 42 });
expect(computeCalls).toBe(1);
});
test('hit: skips compute on second call', async () => {
let computeCalls = 0;
const compute = async () => {
computeCalls += 1;
return { x: 42 };
};
await getCachedOrCompute(
engine,
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
compute,
);
await getCachedOrCompute(
engine,
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
compute,
);
expect(computeCalls).toBe(1);
});
test('D3: bumping cluster_generation invalidates the cache', async () => {
let computeCalls = 0;
const compute = async () => {
computeCalls += 1;
return { x: computeCalls };
};
await getCachedOrCompute(
engine,
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
compute,
);
expect(computeCalls).toBe(1);
// Bump generation — next call should recompute.
await bumpClusterGeneration(engine);
const second = await getCachedOrCompute(
engine,
{ symbol_qualified: 'foo', depth: 3, source_id: 'default' },
compute,
);
expect(computeCalls).toBe(2);
expect(second).toEqual({ x: 2 });
});
});