mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 08:53:22 +00:00
Wave-assembled from PR #4010 by @kyle944. Co-Authored-By: Kyle Cooper <kyle@erudireworkforce.com>
179 lines
6.4 KiB
TypeScript
179 lines
6.4 KiB
TypeScript
/**
|
|
* v0.20.0 Cathedral II Layer 5 (A1) — code-edges engine method tests.
|
|
*
|
|
* Tests addCodeEdges / deleteCodeEdgesForChunks / getCallersOf /
|
|
* getCalleesOf / getEdgesByChunk against real PGLite. End-to-end
|
|
* importCodeFile integration is covered in code-edges-integration.test.ts.
|
|
*/
|
|
|
|
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
|
|
|
describe('Layer 5 (A1) — code-edges engine methods', () => {
|
|
let engine: PGLiteEngine;
|
|
let chunkA: number;
|
|
let chunkB: number;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({});
|
|
await engine.initSchema();
|
|
|
|
// Create two code pages with one chunk each.
|
|
await engine.putPage('src-a-ts', {
|
|
type: 'code', page_kind: 'code',
|
|
title: 'src/a.ts (typescript)',
|
|
compiled_truth: 'export function run() { return helper(); }',
|
|
timeline: '',
|
|
});
|
|
await engine.upsertChunks('src-a-ts', [{
|
|
chunk_index: 0,
|
|
chunk_text: 'export function run() { return helper(); }',
|
|
chunk_source: 'compiled_truth',
|
|
language: 'typescript',
|
|
symbol_name: 'run',
|
|
symbol_type: 'function',
|
|
symbol_name_qualified: 'run',
|
|
}]);
|
|
|
|
await engine.putPage('src-b-ts', {
|
|
type: 'code', page_kind: 'code',
|
|
title: 'src/b.ts (typescript)',
|
|
compiled_truth: 'export function helper() { return 1; }',
|
|
timeline: '',
|
|
});
|
|
await engine.upsertChunks('src-b-ts', [{
|
|
chunk_index: 0,
|
|
chunk_text: 'export function helper() { return 1; }',
|
|
chunk_source: 'compiled_truth',
|
|
language: 'typescript',
|
|
symbol_name: 'helper',
|
|
symbol_type: 'function',
|
|
symbol_name_qualified: 'helper',
|
|
}]);
|
|
|
|
const aChunks = await engine.getChunks('src-a-ts');
|
|
const bChunks = await engine.getChunks('src-b-ts');
|
|
chunkA = aChunks[0]!.id;
|
|
chunkB = bChunks[0]!.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
}, 30_000);
|
|
|
|
test('addCodeEdges inserts unresolved rows into code_edges_symbol', async () => {
|
|
const inserted = await engine.addCodeEdges([{
|
|
from_chunk_id: chunkA,
|
|
to_chunk_id: null,
|
|
from_symbol_qualified: 'run',
|
|
to_symbol_qualified: 'helper',
|
|
edge_type: 'calls',
|
|
}]);
|
|
expect(inserted).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
test('getCallersOf finds the caller by short name (unresolved path)', async () => {
|
|
const results = await engine.getCallersOf('helper', { allSources: true });
|
|
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
const hit = results.find(r => r.from_symbol_qualified === 'run');
|
|
expect(hit).toBeDefined();
|
|
expect(hit!.resolved).toBe(false); // unresolved (from code_edges_symbol)
|
|
expect(hit!.to_symbol_qualified).toBe('helper');
|
|
expect(hit!.edge_type).toBe('calls');
|
|
});
|
|
|
|
test('getCalleesOf finds outbound edges', async () => {
|
|
const results = await engine.getCalleesOf('run', { allSources: true });
|
|
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
expect(results[0]!.to_symbol_qualified).toBe('helper');
|
|
});
|
|
|
|
test('addCodeEdges is idempotent (ON CONFLICT DO NOTHING)', async () => {
|
|
// Re-inserting the same edge returns 0 insertions.
|
|
const inserted = await engine.addCodeEdges([{
|
|
from_chunk_id: chunkA,
|
|
to_chunk_id: null,
|
|
from_symbol_qualified: 'run',
|
|
to_symbol_qualified: 'helper',
|
|
edge_type: 'calls',
|
|
}]);
|
|
expect(inserted).toBe(0);
|
|
});
|
|
|
|
test('addCodeEdges resolved path lands in code_edges_chunk', async () => {
|
|
const inserted = await engine.addCodeEdges([{
|
|
from_chunk_id: chunkA,
|
|
to_chunk_id: chunkB,
|
|
from_symbol_qualified: 'run',
|
|
to_symbol_qualified: 'helper',
|
|
edge_type: 'calls',
|
|
}]);
|
|
expect(inserted).toBeGreaterThanOrEqual(1);
|
|
|
|
// getCallersOf UNIONs both tables; resolved hit should now appear
|
|
// alongside the unresolved one.
|
|
const results = await engine.getCallersOf('helper', { allSources: true });
|
|
const resolvedCount = results.filter(r => r.resolved).length;
|
|
expect(resolvedCount).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
test('getEdgesByChunk returns edges for a known chunk', async () => {
|
|
const outgoing = await engine.getEdgesByChunk(chunkA, { direction: 'out' });
|
|
expect(outgoing.length).toBeGreaterThanOrEqual(1);
|
|
const incoming = await engine.getEdgesByChunk(chunkB, { direction: 'in' });
|
|
expect(incoming.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
test('deleteCodeEdgesForChunks removes rows in both directions', async () => {
|
|
await engine.deleteCodeEdgesForChunks([chunkA]);
|
|
const after = await engine.getEdgesByChunk(chunkA, { direction: 'both' });
|
|
expect(after).toEqual([]);
|
|
// code_edges_symbol rows from chunkA are also gone.
|
|
const callers = await engine.getCallersOf('helper', { allSources: true });
|
|
const fromA = callers.filter(r => r.from_chunk_id === chunkA);
|
|
expect(fromA).toEqual([]);
|
|
});
|
|
|
|
test('empty edge input returns 0 without SQL', async () => {
|
|
const inserted = await engine.addCodeEdges([]);
|
|
expect(inserted).toBe(0);
|
|
});
|
|
|
|
test('batches edge writes before PGLite parameter overflow poisons later writes', async () => {
|
|
// Six binds per unresolved edge. 5,462 rows require 32,772 binds, which
|
|
// crosses PGLite's signed-int16 ceiling. Before batching, addCodeEdges
|
|
// misleadingly returned success but every subsequent putPage produced no
|
|
// row until the process restarted.
|
|
const edgeCount = 5_462;
|
|
const inserted = await engine.addCodeEdges(
|
|
Array.from({ length: edgeCount }, (_, i) => ({
|
|
from_chunk_id: chunkA,
|
|
to_chunk_id: null,
|
|
from_symbol_qualified: 'run',
|
|
to_symbol_qualified: `overflow-target-${i}`,
|
|
edge_type: 'calls',
|
|
})),
|
|
);
|
|
expect(inserted).toBe(edgeCount);
|
|
|
|
const rows = await engine.executeRaw<{ count: number | string }>(
|
|
`SELECT COUNT(*) AS count
|
|
FROM code_edges_symbol
|
|
WHERE from_chunk_id = $1
|
|
AND to_symbol_qualified LIKE 'overflow-target-%'`,
|
|
[chunkA],
|
|
);
|
|
expect(Number(rows[0]!.count)).toBe(edgeCount);
|
|
|
|
const page = await engine.putPage('post-edge-batch-smoke', {
|
|
type: 'code', page_kind: 'code',
|
|
title: 'Post-edge batch smoke',
|
|
compiled_truth: 'export const stillWritable = true;',
|
|
timeline: '',
|
|
});
|
|
expect(page.slug).toBe('post-edge-batch-smoke');
|
|
expect((await engine.getPage('post-edge-batch-smoke'))?.slug).toBe(page.slug);
|
|
});
|
|
});
|