/** * @module tests/unit/brainy/graph-native-routing * @description Graph engine — NATIVE seam coverage. brain.graph.subgraph/export * route to a registered GraphAccelerationProvider and hydrate its columnar * `Subgraph` (node ints -> ids, depth alignment, edge verb-ints -> Relations). * In production that provider is cor's native engine, cross-layer-tested on * bxl9000; brainy CI never registers one, so graphSubgraphNative / * graphExportNative / hydrateNativeSubgraph + the provider-resolution + routing * were previously UNEXERCISED — a return-shape or hydration-alignment drift would * pass CI silently. This registers a faithful MOCK provider (returning a columnar * Subgraph built from the brain's REAL ints, so hydration resolves to real * entities/relations) to lock those paths. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { Brainy } from '../../../src/index.js' import { NounType, VerbType } from '../../../src/types/graphTypes.js' import { createTestConfig } from '../../helpers/test-factory.js' /** A stateful mock GraphAccelerationProvider; the test sets the columnar payloads. */ function makeMockAccel() { const calls = { traverse: 0, cursorOpen: 0, cursorNext: 0, cursorClose: 0, rank: 0, communities: 0, path: 0 } const state: { calls: typeof calls traverseResult: any cursorChunks: any[] rankResult: any communitiesResult: any pathResult: any lastTraverseSeeds: any } = { calls, traverseResult: null, cursorChunks: [], rankResult: null, communitiesResult: null, pathResult: null, lastTraverseSeeds: undefined } const empty = { nodeInts: new BigInt64Array(0), scores: new Float64Array(0) } const emptyCommunities = { nodeInts: new BigInt64Array(0), communityIds: new Uint32Array(0), communityCount: 0 } const provider = { isInitialized: true, traverse: async (seeds: any) => { calls.traverse++ state.lastTraverseSeeds = seeds return state.traverseResult }, edgesForNode: async () => state.traverseResult, graphCursorOpen: async () => { calls.cursorOpen++ return 'mock-handle' }, graphCursorNext: async () => { calls.cursorNext++ const subgraph = state.cursorChunks.shift() return { subgraph, done: state.cursorChunks.length === 0 } }, graphCursorClose: async () => { calls.cursorClose++ }, rank: async () => { calls.rank++ return state.rankResult ?? empty }, communities: async () => { calls.communities++ return state.communitiesResult ?? emptyCommunities }, path: async () => { calls.path++ return state.pathResult }, sample: async () => state.traverseResult, mostConnected: async () => empty } return { provider, state } } describe('brain.graph.* native routing + columnar hydration (native seam)', () => { let brain: Brainy let mock: ReturnType let a: string, b: string, c: string // Build a faithful columnar Subgraph from the brain's REAL ints, so brainy's // hydration resolves the node ints to ids and the verb ints to Relations. async function realSubgraph(withUnresolvable = false) { const gei = (id: string): bigint => (brain as any).graphEntityInt(id) const gi = (brain as any).graphIndex const intA = gei(a), intB = gei(b), intC = gei(c) const vAB = (await gi.getVerbIdsBySource(intA))[0] as bigint // verb int a->b const vBC = (await gi.getVerbIdsBySource(intB))[0] as bigint // verb int b->c const nodeInts = [intA, intB, intC] const depths = [0, 1, 2] if (withUnresolvable) { nodeInts.push(999_999_999n) // a never-assigned int (simulates a deleted/unknown node) depths.push(3) } return { nodes: BigInt64Array.from(nodeInts), nodeDepth: Uint8Array.from(depths), edgeSources: BigInt64Array.from([intA, intB]), edgeTargets: BigInt64Array.from([intB, intC]), edgeVerbInts: BigInt64Array.from([vAB, vBC]), edgeTypes: Uint16Array.from([0, 0]), truncated: false } } beforeEach(async () => { mock = makeMockAccel() brain = new Brainy(createTestConfig()) brain.use({ name: 'mock-graph-accel', activate: async (ctx: any) => { ctx.registerProvider('graphAcceleration', mock.provider) return true } } as any) await brain.init() a = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'A' }) b = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'B' }) c = await brain.add({ type: NounType.Project, subtype: 'milestone', data: 'C' }) await brain.relate({ from: a, to: b, type: VerbType.RelatedTo, subtype: 'colleague' }) await brain.relate({ from: b, to: c, type: VerbType.ParticipatesIn, subtype: 'assignment' }) }) afterEach(async () => { await brain.close() }) it('subgraph() routes to the native provider and hydrates the columnar result', async () => { mock.state.traverseResult = await realSubgraph() const view = await brain.graph.subgraph(a, { depth: 2 }) expect(mock.state.calls.traverse).toBe(1) // native path, not the TS fallback const byId = new Map(view.nodes.map((n) => [n.id, n])) expect(new Set(byId.keys())).toEqual(new Set([a, b, c])) // node int -> id expect(byId.get(a)?.depth).toBe(0) expect(byId.get(c)?.depth).toBe(2) // depth column aligned to node column expect(byId.get(c)?.type).toBe(NounType.Project) // node type hydrated via batchGet const pairs = view.edges.map((e) => `${e.from}->${e.to}`).sort() expect(pairs).toEqual([`${a}->${b}`, `${b}->${c}`].sort()) // verb int -> Relation }) it('keeps node<->depth alignment when a node int does not resolve (deleted/unknown)', async () => { mock.state.traverseResult = await realSubgraph(true) // appends an unresolvable int at depth 3 const view = await brain.graph.subgraph(a, { depth: 3 }) const byId = new Map(view.nodes.map((n) => [n.id, n])) // The 3 real nodes keep their CORRECT depths — the unresolvable int is dropped, // not collapsed into the array (which would shift every later depth). expect(byId.get(a)?.depth).toBe(0) expect(byId.get(b)?.depth).toBe(1) expect(byId.get(c)?.depth).toBe(2) expect(view.nodes.length).toBe(3) }) it('export() routes to the native graph cursor, hydrates chunks, and always closes', async () => { mock.state.cursorChunks = [await realSubgraph()] const chunks: any[] = [] for await (const v of brain.graph.export()) chunks.push(v) expect(mock.state.calls.cursorOpen).toBe(1) expect(mock.state.calls.cursorClose).toBe(1) // cursor released even on normal completion const nodes = new Set(chunks.flatMap((c) => c.nodes.map((n: any) => n.id))) expect(nodes).toEqual(new Set([a, b, c])) const edges = chunks.flatMap((c) => c.edges.map((e: any) => `${e.from}->${e.to}`)).sort() expect(edges).toEqual([`${a}->${b}`, `${b}->${c}`].sort()) }) it('resolves a provider registered as a FACTORY (storage) => provider, not just an instance', async () => { const m = makeMockAccel() const fb = new Brainy(createTestConfig()) fb.use({ name: 'mock-graph-accel-factory', activate: async (ctx: any) => { ctx.registerProvider('graphAcceleration', () => m.provider) // factory form return true } } as any) await fb.init() const x = await fb.add({ type: NounType.Person, subtype: 'employee', data: 'X' }) const y = await fb.add({ type: NounType.Person, subtype: 'employee', data: 'Y' }) await fb.relate({ from: x, to: y, type: VerbType.RelatedTo, subtype: 'colleague' }) const gei = (id: string): bigint => (fb as any).graphEntityInt(id) const gi = (fb as any).graphIndex const ix = gei(x), iy = gei(y) const vxy = (await gi.getVerbIdsBySource(ix))[0] as bigint m.state.traverseResult = { nodes: BigInt64Array.from([ix, iy]), nodeDepth: Uint8Array.from([0, 1]), edgeSources: BigInt64Array.from([ix]), edgeTargets: BigInt64Array.from([iy]), edgeVerbInts: BigInt64Array.from([vxy]), edgeTypes: Uint16Array.from([0]), truncated: false } const view = await fb.graph.subgraph(x, { depth: 1 }) expect(m.state.calls.traverse).toBe(1) // factory was invoked + provider routed expect(new Set(view.nodes.map((n) => n.id))).toEqual(new Set([x, y])) await fb.close() }) it('rank() routes to the native provider and hydrates node ints -> ids, order preserved', async () => { const gei = (id: string): bigint => (brain as any).graphEntityInt(id) // Provider returns DESCENDING scores: c, then a, then b. mock.state.rankResult = { nodeInts: BigInt64Array.from([gei(c), gei(a), gei(b)]), scores: Float64Array.from([0.5, 0.3, 0.2]) } const ranked = await brain.graph.rank() expect(mock.state.calls.rank).toBe(1) // native path, not the TS PageRank fallback expect(ranked.map((r) => r.id)).toEqual([c, a, b]) // int -> id, provider order kept expect(ranked[0].score).toBe(0.5) const top2 = await brain.graph.rank({ topK: 2 }) expect(top2.map((r) => r.id)).toEqual([c, a]) }) it('communities() routes to the native provider and buckets ids by community label', async () => { const gei = (id: string): bigint => (brain as any).graphEntityInt(id) // a,b in community 0; c alone in community 1. mock.state.communitiesResult = { nodeInts: BigInt64Array.from([gei(a), gei(b), gei(c)]), communityIds: Uint32Array.from([0, 0, 1]), communityCount: 2 } const { groups, count } = await brain.graph.communities() expect(mock.state.calls.communities).toBe(1) // native path, not the TS fallback expect(count).toBe(2) const asSets = groups.map((g) => new Set(g)) expect(asSets).toContainEqual(new Set([a, b])) expect(asSets).toContainEqual(new Set([c])) expect(groups[0]).toHaveLength(2) // largest group first }) it('path() routes to the native provider and hydrates node + verb ints', async () => { const gei = (id: string): bigint => (brain as any).graphEntityInt(id) const gi = (brain as any).graphIndex const vAB = (await gi.getVerbIdsBySource(gei(a)))[0] as bigint const vBC = (await gi.getVerbIdsBySource(gei(b)))[0] as bigint mock.state.pathResult = { nodeInts: BigInt64Array.from([gei(a), gei(b), gei(c)]), edgeVerbInts: BigInt64Array.from([vAB, vBC]), cost: 2 } const route = await brain.graph.path(a, c) expect(mock.state.calls.path).toBe(1) // native path, not the TS BFS/Dijkstra fallback expect(route?.nodes).toEqual([a, b, c]) // node ints -> ids expect(route?.relationships).toHaveLength(2) // verb ints -> verb-id strings expect(route?.cost).toBe(2) }) it('path() returns null when the native provider reports unreachable', async () => { mock.state.pathResult = null expect(await brain.graph.path(a, c)).toBeNull() expect(mock.state.calls.path).toBe(1) }) it('subgraph(query) forwards the metadata universe to traverse as an OpaqueIdSet (query→expand #61)', async () => { // The native metadata index would return its roaring filter result as a Buffer; // stub that producer and assert it reaches traverse WITHOUT id materialization. const sentinel = new Uint8Array([0x01, 0x02, 0x03]) ;(brain as any).metadataIndex.getIdSetForFilter = async () => sentinel mock.state.traverseResult = await realSubgraph() await brain.graph.subgraph({ type: NounType.Person }, { depth: 1 }) expect(mock.state.calls.traverse).toBe(1) // The opaque Buffer is the traverse seed argument — passed straight through. expect(mock.state.lastTraverseSeeds).toBe(sentinel) }) it('subgraph(query) materializes seeds via find() when no opaque producer exists', async () => { // No getIdSetForFilter on the (real) metadata index → general path: find() runs, // its matched ids resolve to entity ints, and those seed the native traverse. mock.state.traverseResult = await realSubgraph() await brain.graph.subgraph({ type: NounType.Person }, { depth: 1 }) expect(mock.state.calls.traverse).toBe(1) expect(Array.isArray(mock.state.lastTraverseSeeds)).toBe(true) // bigint[], not a Buffer expect(typeof (mock.state.lastTraverseSeeds as unknown[])[0]).toBe('bigint') }) })