/** * @module tests/unit/brainy/related-visibility-fast-path * @description Graph-perf #1 (8.0): the visibility-aware fast adjacency path. * * `related({ from })` / `related({ to })` route through the O(degree) * GraphAdjacencyIndex fast paths in `storage.getVerbs`. Before this fix, the * default visibility exclusion (`excludeVisibility: ['internal','system']`, * which every default `related()` sets) DISQUALIFIED those fast paths and forced * a full O(E) shard scan — the root cause of multi-second per-node edge lookups * on large graphs. The fast paths now hydrate each candidate's metadata and * apply the subtype / visibility filters on the small O(degree) candidate set. * * Two observable contracts are pinned here (the perf change itself is structural * — these prove the fast path produces the SAME correct results the scan did): * 1. default `related()` excludes `internal` / `system` edges; `includeInternal` * / `includeSystem` opt them back in. * 2. returned `Relation`s now carry `visibility` (previously dropped by * `hydrateVerbWithMetadata`, so `related({ includeInternal })` could not even * report which edges were internal). */ 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' describe('related() visibility-aware fast adjacency (graph-perf #1)', () => { let brain: Brainy let a: string, b: string, c: string, d: string beforeEach(async () => { brain = new Brainy(createTestConfig()) 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.Person, subtype: 'employee', data: 'C' }) d = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'D' }) // A → B public, A → C internal, A → D public. await brain.relate({ from: a, to: b, type: VerbType.RelatedTo, subtype: 'colleague' }) await brain.relate({ from: a, to: c, type: VerbType.RelatedTo, subtype: 'colleague', visibility: 'internal' }) await brain.relate({ from: a, to: d, type: VerbType.RelatedTo, subtype: 'colleague' }) }) afterEach(async () => { await brain.close() }) it('default related({ from }) excludes internal edges (out-direction fast path)', async () => { const edges = await brain.related({ from: a }) const targets = edges.map((e) => e.to).sort() expect(targets).toEqual([b, d].sort()) // None of the default results carry a non-public visibility. expect(edges.every((e) => e.visibility === undefined || e.visibility === 'public')).toBe(true) }) it('related({ from, includeInternal }) surfaces the internal edge AND reports its visibility', async () => { const edges = await brain.related({ from: a, includeInternal: true }) expect(edges.map((e) => e.to).sort()).toEqual([b, c, d].sort()) const internalEdge = edges.find((e) => e.to === c) // The latent-bug fix: visibility now flows through hydration to the Relation. expect(internalEdge?.visibility).toBe('internal') }) it('default related({ to }) excludes an internal edge (in-direction fast path)', async () => { // B has one public in-edge; C has one internal in-edge. const intoB = await brain.related({ to: b }) expect(intoB.map((e) => e.from)).toEqual([a]) const intoC = await brain.related({ to: c }) expect(intoC).toHaveLength(0) // internal edge hidden by default const intoCAll = await brain.related({ to: c, includeInternal: true }) expect(intoCAll.map((e) => e.from)).toEqual([a]) expect(intoCAll[0]?.visibility).toBe('internal') }) it('subtype filter still works on the fast path (returns only matching, excludes internal)', async () => { const colleagues = await brain.related({ from: a, subtype: 'colleague' }) expect(colleagues.map((e) => e.to).sort()).toEqual([b, d].sort()) }) })