2026-06-24 10:55:12 -07:00
/ * *
* @module tests / unit / brainy / graph - adjacency - cold - load
* @description 7.33 . 2 — the graph - adjacency cold - load consistency guard . A native
* graph provider can load its relationship COUNT ( manifest ) on a cold open but fail
* to load the source → target adjacency ( observed with cor 2.7 . 5 ' s NativeGraphAdjacencyIndex
* on mmap - filesystem : the SSTable - segment load is swallowed ) . The result is
* ` find({ connected }) ` / ` neighbors() ` / ` related() ` silently returning ` [] ` despite
* persisted edges . Brainy now probes one known persisted edge on the first graph read ;
* if the index claims edges but the source resolves to none , it forces a rebuild from
* storage , and only if even that fails does it throw a loud , catchable error instead of
* serving ` [] ` as truth . These exercise the guard against a stubbed graph index .
* /
import { describe , it , expect } from 'vitest'
import { Brainy } from '../../../src/index.js'
import { GraphIndexNotReadyError } from '../../../src/errors/brainyError.js'
import { createTestConfig } from '../../helpers/test-factory.js'
/** Replace the brain's graph index with a stub that models a (possibly broken) cold load. */
function stubGraphIndex (
brain : any ,
opts : { size : number ; neighborsBeforeRebuild : string [ ] ; neighborsAfterRebuild? : string [ ] }
) {
let rebuilt = false
const calls = { rebuild : 0 , getNeighbors : 0 }
brain . graphIndex = {
size : ( ) = > opts . size ,
getNeighbors : async ( ) = > {
calls . getNeighbors ++
return rebuilt ? opts . neighborsAfterRebuild ? ? [ ] : opts . neighborsBeforeRebuild
} ,
rebuild : async ( ) = > {
calls . rebuild ++
rebuilt = true
} ,
flush : async ( ) = > { } ,
close : async ( ) = > { }
}
return calls
}
async function freshBrain ( ) : Promise < any > {
const brain : any = new Brainy ( createTestConfig ( ) )
await brain . init ( )
brain . _graphAdjacencyVerified = false
return brain
}
describe ( '7.33.2 graph-adjacency cold-load consistency guard' , ( ) = > {
it ( 'is a no-op when the adjacency is live (a persisted edge resolves to neighbors)' , async ( ) = > {
const brain = await freshBrain ( )
const calls = stubGraphIndex ( brain , { size : 12 , neighborsBeforeRebuild : [ 'n1' ] } )
brain . storage . getVerbs = async ( ) = > ( { items : [ { id : 'v1' , sourceId : 's1' , targetId : 't1' } ] , hasMore : false } )
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 0 )
await brain . close ( )
} )
it ( 'force-rebuilds when the index reports edges but the source probes empty' , async ( ) = > {
const brain = await freshBrain ( )
const calls = stubGraphIndex ( brain , { size : 12 , neighborsBeforeRebuild : [ ] , neighborsAfterRebuild : [ 'n1' ] } )
brain . storage . getVerbs = async ( ) = > ( { items : [ { id : 'v1' , sourceId : 's1' , targetId : 't1' } ] , hasMore : false } )
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 1 ) // detected the empty adjacency + rebuilt it
// Cached: a second call does not re-probe or re-rebuild.
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 1 )
await brain . close ( )
} )
it ( 'throws GraphIndexNotReadyError when even a rebuild cannot load the adjacency' , async ( ) = > {
const brain = await freshBrain ( )
stubGraphIndex ( brain , { size : 12 , neighborsBeforeRebuild : [ ] , neighborsAfterRebuild : [ ] } ) // never loads
brain . storage . getVerbs = async ( ) = > ( { items : [ { id : 'v1' , sourceId : 's1' , targetId : 't1' } ] , hasMore : false } )
2026-06-29 16:40:02 -07:00
await expect ( brain . verifyGraphAdjacencyLive ( ) ) . rejects . toBeInstanceOf ( GraphIndexNotReadyError )
2026-06-24 10:55:12 -07:00
await brain . close ( )
} )
it ( 'no-op when no edges are claimed (size 0) — never even probes storage' , async ( ) = > {
const brain = await freshBrain ( )
const calls = stubGraphIndex ( brain , { size : 0 , neighborsBeforeRebuild : [ ] } )
let probedStorage = false
brain . storage . getVerbs = async ( ) = > {
probedStorage = true
return { items : [ ] , hasMore : false }
}
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 0 )
expect ( probedStorage ) . toBe ( false )
await brain . close ( )
} )
it ( 'no-op when storage has no edges (stale count) — does not rebuild or throw' , async ( ) = > {
const brain = await freshBrain ( )
const calls = stubGraphIndex ( brain , { size : 12 , neighborsBeforeRebuild : [ ] } )
brain . storage . getVerbs = async ( ) = > ( { items : [ ] , hasMore : false } )
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 0 )
await brain . close ( )
} )
it ( 'does not loop or break the query on a transient probe failure (re-checks next time)' , async ( ) = > {
const brain = await freshBrain ( )
const calls = stubGraphIndex ( brain , { size : 12 , neighborsBeforeRebuild : [ ] , neighborsAfterRebuild : [ 'n1' ] } )
let firstCall = true
brain . storage . getVerbs = async ( ) = > {
if ( firstCall ) {
firstCall = false
throw new Error ( 'transient storage hiccup' )
}
return { items : [ { id : 'v1' , sourceId : 's1' , targetId : 't1' } ] , hasMore : false }
}
// First call: probe throws transiently → swallowed (no GraphIndexNotReadyError), flag reset.
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 0 )
expect ( brain . _graphAdjacencyVerified ) . toBe ( false ) // re-check allowed
// Second call: storage works → detects the empty adjacency + rebuilds.
2026-06-29 16:40:02 -07:00
await brain . verifyGraphAdjacencyLive ( )
2026-06-24 10:55:12 -07:00
expect ( calls . rebuild ) . toBe ( 1 )
await brain . close ( )
} )
} )