test(hygiene): close every brain the remaining suites create
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:
parent
be307a1579
commit
656d9f6f92
6 changed files with 50 additions and 5 deletions
|
|
@ -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' })
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(() => {})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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(() => {})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue