test(hygiene): close every brain the remaining suites create
Some checks are pending
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run

id-normalization.test.ts's makeBrain() and degraded-reads-surfaced.test.ts's
per-test brains had nothing tracking them — both now use a describe-scoped
opened[] array drained by afterEach.

find-hybrid-filter-before-hydrate.test.ts had two beforeAll-built brains
(one per describe block) with no matching afterAll.

multi-process-safety.test.ts and plugin-autodetect.test.ts/plugin.test.ts
left a brain whose init() was expected to reject (a rejected init() still
registers the instance in Brainy's global instance registry — the
constructor does that unconditionally — so it still needs close() to
deregister, or the process-level shutdown hooks never see the registry go
idle for the rest of the run).
This commit is contained in:
David Snelling 2026-09-03 09:18:57 -07:00
parent be307a1579
commit 656d9f6f92
6 changed files with 50 additions and 5 deletions

View file

@ -31,7 +31,7 @@
* never the legs. And the text leg is asked about the universe's ids only
* what it marshals is bounded by the universe, not by the store.
*/
import { describe, it, expect, beforeAll, vi } from 'vitest'
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType, VerbType } from '../../src/types/graphTypes'
import { rankIndicesByScore, reorderByIndices } from '../../src/utils/resultRanking'
@ -287,6 +287,10 @@ describe('hybrid find: filter before hydrate — the answer is unchanged', () =>
expect(typeof (brain as any).metadataIndex.getIdSetForFilter).not.toBe('function')
})
afterAll(async () => {
await brain.close()
})
it('the fixture does not truncate the text leg — the universe covers every text match', async () => {
const index = (brain as any).metadataIndex
const textMatches = await index.getIdsForTextQuery(QUERY)
@ -553,6 +557,10 @@ describe('hybrid find: the text leg ranks inside the filter, not around it', ()
}
})
afterAll(async () => {
await brain.close()
})
it('the old order let the filter consume the whole text leg', async () => {
const index = (brain as any).metadataIndex
const universe: string[] = await (brain as any).filterIdsBelted({ lane: 'alpha' })

View file

@ -18,7 +18,7 @@
* All entities carry explicit 384-dim vectors so no test invokes the embedder.
*/
import { describe, it, expect } from 'vitest'
import { describe, it, expect, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType, VerbType } from '../../src/types/graphTypes.js'
import { v5, v7, isUUID } from '../../src/universal/uuid.js'
@ -37,8 +37,15 @@ async function makeBrain(): Promise<Brainy> {
}
describe('id normalization — transparent string-key round-trips', () => {
const opened: Brainy[] = []
afterEach(async () => {
for (const b of opened.splice(0)) await b.close().catch(() => {})
})
it('1. add() returns v5(key); get(key) and get(returnedId) both resolve; _originalId preserved', async () => {
const brain = await makeBrain()
opened.push(brain)
const returnedId = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
@ -60,6 +67,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('2. relate() by string keys; related(key) and related({from:key}) return the edge to v5(toKey)', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
await brain.add({ id: 'doc-1', vector: vec(2), type: NounType.Document })
@ -85,6 +93,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('3. update() by string key reflects on get(key)', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { role: 'admin' } })
await brain.update({ id: 'user-1', metadata: { role: 'owner' } })
@ -98,6 +107,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('4. remove() by string key deletes; get(key) is null', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
expect(await brain.get('user-1')).not.toBeNull()
@ -110,6 +120,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('5. find({ connected: { from: key } }) resolves the anchor key', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
await brain.add({ id: 'doc-1', vector: vec(2), type: NounType.Document })
@ -122,6 +133,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('6. transact() add+relate by string keys round-trips with consistent canonical ids', async () => {
const brain = await makeBrain()
opened.push(brain)
// Seed user-1 so the relate op has a target to point at.
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
@ -149,6 +161,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('7. addMany() + relateMany() with string ids round-trip', async () => {
const brain = await makeBrain()
opened.push(brain)
const added = await brain.addMany({
items: [
@ -175,6 +188,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('8. determinism: same key maps to same UUID — two adds upsert ONE entity, not two', async () => {
const brain = await makeBrain()
opened.push(brain)
const id1 = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { n: 1 } })
const id2 = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { n: 2 } })
@ -193,6 +207,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('9. valid-UUID passthrough: a real UUID is kept verbatim with NO _originalId', async () => {
const brain = await makeBrain()
opened.push(brain)
const realUuid = v7()
const returnedId = await brain.add({ id: realUuid, vector: vec(5), type: NounType.Thing })
@ -207,6 +222,7 @@ describe('id normalization — transparent string-key round-trips', () => {
it('10. no-id add() mints a v7; newId() mints a v7', async () => {
const brain = await makeBrain()
opened.push(brain)
const autoId = await brain.add({ vector: vec(6), type: NounType.Thing })
expect(isUUID(autoId)).toBe(true)

View file

@ -107,7 +107,11 @@ describe('Multi-process safety + read-only mode', () => {
const blocked = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir } })
await expect(blocked.init()).rejects.toThrow(/another writer holds/i)
// Don't track `blocked` for afterEach cleanup since init failed.
// A rejected init() still registered `blocked` in Brainy's global
// instance registry (the constructor does that unconditionally) — close()
// is safe to call even though init() never completed, and is what
// deregisters it (and, once idle, the process-level shutdown hooks).
await blocked.close().catch(() => {})
})
it('takes over a STALE foreign lock (dead PID + old heartbeat) and claims atomically', async () => {
@ -151,6 +155,7 @@ describe('Multi-process safety + read-only mode', () => {
const err: any = await blocked.init().catch((e) => e)
expect(err.code).toBe('BRAINY_WRITER_LOCKED')
expect(err.lockInfo?.pid).toBe(otherPid)
await blocked.close().catch(() => {})
})
it('release drains an in-flight heartbeat — no phantom lock re-created after unlink', async () => {

View file

@ -19,13 +19,19 @@ import { prodLog } from '../../../src/utils/logger.js'
const UUID = (suffix: string): string => `00000000-0000-4000-8000-0000000000${suffix}`
describe('Finding 10 — degraded derived-index state is surfaced on reads', () => {
const opened: Brainy[] = []
beforeEach(() => {
process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true'
})
afterEach(() => vi.restoreAllMocks())
afterEach(async () => {
vi.restoreAllMocks()
for (const b of opened.splice(0)) await b.close().catch(() => {})
})
it('checkHealth() reports adopt-forward degraded ids as unhealthy', async () => {
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
;(brain as any)._indexDegradedIds.add(UUID('de'))
@ -37,6 +43,7 @@ describe('Finding 10 — degraded derived-index state is surfaced on reads', ()
it('find()/get() warn loudly while degraded, ONCE, then repairIndex() clears it', async () => {
const warn = vi.spyOn(prodLog, 'warn').mockImplementation(() => {})
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
await brain.add({ id: UUID('a1'), data: 'x', type: NounType.Document })
;(brain as any)._indexRebuildFailed = new Error('rebuild boom')
@ -59,6 +66,7 @@ describe('Finding 10 — degraded derived-index state is surfaced on reads', ()
it('persistSingleOp records receipt.degraded (widened return type, not dropped)', async () => {
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
// Simulate a degraded receipt by wrapping the generation store's commitSingleOp.
const gs: any = (brain as any).generationStore

View file

@ -89,12 +89,14 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
})
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/installed but failed to load/)
await brain.close().catch(() => {})
})
it('installed but not a valid plugin (missing activate) → init() throws', async () => {
stubImport(async () => ({ default: { name: '@soulcraft/cor' } })) // no activate()
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/not a valid Brainy plugin/)
await brain.close().catch(() => {})
})
it('installed but activation fails → init() throws (activateAll posture applies)', async () => {
@ -108,6 +110,7 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
}))
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/failed to activate/)
await brain.close().catch(() => {})
})
it('plugins: [] and plugins: false → no probe at all (explicit opt-out)', async () => {
@ -132,5 +135,6 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
silent: true
})
await expect(brain.init()).rejects.toThrow(/listed in config\.plugins but could not be loaded/)
await brain.close().catch(() => {})
})
})

View file

@ -298,9 +298,10 @@ describe('Brainy plugin integration', () => {
// must surface as a failed init(), NOT a silent degrade to the default
// engine (the version-coupling guard; see plugin-version-coupling.test.ts).
await expect(brain.init()).rejects.toThrow(/failed to activate|native module not found/)
await brain.close().catch(() => {})
})
it('should use() return this for chaining', () => {
it('should use() return this for chaining', async () => {
const plugin: BrainyPlugin = {
name: 'chain-test',
activate: async () => true
@ -309,5 +310,8 @@ describe('Brainy plugin integration', () => {
const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
const result = brain.use(plugin)
expect(result).toBe(brain)
// Never init()'d — the constructor still registered it in Brainy's global
// instance registry, so it still needs a close() to deregister.
await brain.close().catch(() => {})
})
})