76 lines
3 KiB
TypeScript
76 lines
3 KiB
TypeScript
|
|
/**
|
|||
|
|
* @module tests/unit/brainy/lazy-notready-honor
|
|||
|
|
* @description THE SILENT-EMPTY TRAP pin (found during a fleet adoption,
|
|||
|
|
* SELF-ENGINE-PAIR-STANDARD): under `disableAutoRebuild: true`, the lazy
|
|||
|
|
* first-query path (`ensureIndexesLoaded`) assessed ONLY the vector index's
|
|||
|
|
* readiness — a native METADATA provider reporting not-ready (its strand
|
|||
|
|
* report) never blocked the completion latch, so the promised lazy rebuild
|
|||
|
|
* never fired and every `find()` silently returned `[]` on a populated
|
|||
|
|
* store (measured: 52 entities durable-but-unqueryable, first query
|
|||
|
|
* 0ms/0 rows). The law: a not-ready report from ANY provider falls through
|
|||
|
|
* to the rebuild — never a silent empty.
|
|||
|
|
*
|
|||
|
|
* White-box provider-double pattern per tests/unit/brainy/migration-deference.
|
|||
|
|
*/
|
|||
|
|
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|||
|
|
import { Brainy } from '../../../src/index.js'
|
|||
|
|
import { NounType } from '../../../src/types/graphTypes.js'
|
|||
|
|
import { createTestConfig } from '../../helpers/test-factory.js'
|
|||
|
|
|
|||
|
|
interface BrainInternals {
|
|||
|
|
index: { size(): number }
|
|||
|
|
metadataIndex: { isReady?: () => boolean }
|
|||
|
|
lazyRebuildCompleted: boolean
|
|||
|
|
ensureIndexesLoaded(): Promise<void>
|
|||
|
|
rebuildIndexesIfNeeded(force?: boolean): Promise<void>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const brains: Brainy[] = []
|
|||
|
|
|
|||
|
|
afterEach(async () => {
|
|||
|
|
for (const b of brains.splice(0)) await b.close().catch(() => {})
|
|||
|
|
vi.restoreAllMocks()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
async function warmLazyBrain(): Promise<{ brain: Brainy; internals: BrainInternals }> {
|
|||
|
|
const brain = new Brainy(createTestConfig({ disableAutoRebuild: true }))
|
|||
|
|
await brain.init()
|
|||
|
|
brains.push(brain)
|
|||
|
|
for (let i = 0; i < 3; i++) {
|
|||
|
|
await brain.add({ data: `row ${i}`, type: NounType.Document, metadata: { i } })
|
|||
|
|
}
|
|||
|
|
const internals = brain as unknown as BrainInternals
|
|||
|
|
internals.lazyRebuildCompleted = false // simulate the cold first query
|
|||
|
|
return { brain, internals }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
describe('lazy path honors EVERY provider’s not-ready report', () => {
|
|||
|
|
it('a not-ready METADATA provider blocks the completion latch and fires the rebuild', async () => {
|
|||
|
|
const { internals } = await warmLazyBrain()
|
|||
|
|
|
|||
|
|
// The trap's shape: vector side looks fine (populated), metadata
|
|||
|
|
// provider says NOT ready — the old gate latched complete here.
|
|||
|
|
;(internals.metadataIndex as { isReady?: () => boolean }).isReady = () => false
|
|||
|
|
const rebuildSpy = vi
|
|||
|
|
.spyOn(internals, 'rebuildIndexesIfNeeded')
|
|||
|
|
.mockResolvedValue(undefined)
|
|||
|
|
|
|||
|
|
await internals.ensureIndexesLoaded()
|
|||
|
|
|
|||
|
|
expect(rebuildSpy, 'not-ready metadata provider must fire the lazy rebuild').toHaveBeenCalledWith(true)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('control: all providers ready/unknown+populated → latch completes, no rebuild', async () => {
|
|||
|
|
const { internals } = await warmLazyBrain()
|
|||
|
|
;(internals.metadataIndex as { isReady?: () => boolean }).isReady = () => true
|
|||
|
|
const rebuildSpy = vi
|
|||
|
|
.spyOn(internals, 'rebuildIndexesIfNeeded')
|
|||
|
|
.mockResolvedValue(undefined)
|
|||
|
|
|
|||
|
|
await internals.ensureIndexesLoaded()
|
|||
|
|
|
|||
|
|
expect(rebuildSpy).not.toHaveBeenCalled()
|
|||
|
|
expect(internals.lazyRebuildCompleted).toBe(true)
|
|||
|
|
})
|
|||
|
|
})
|