2025-10-24 12:59:41 -07:00
/ * *
* Remaining APIs Comprehensive Test ( v4 . 4.0 )
*
* Tests APIs that weren ' t covered in all - apis - comprehensive . test . ts :
* - brain . updateMany ( )
* - brain . import ( ) ( CSV / Excel / PDF with VFS )
* - brain . clear ( )
* - vfs file operations ( unlink , rmdir , rename , copy , move )
* /
import { describe , it , expect , beforeAll , afterAll } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js'
import * as fs from 'fs'
import * as path from 'path'
describe ( 'Remaining APIs Comprehensive Test' , ( ) = > {
const testDir = path . join ( process . cwd ( ) , 'test-remaining-apis' )
let brain : Brainy
beforeAll ( async ( ) = > {
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true , force : true } )
}
fs . mkdirSync ( testDir , { recursive : true } )
feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1)
Brainy 8.0 makes subtype required by default on every public write path
(`add`, `addMany`, `update`, `relate`, `relateMany`, `updateRelation`,
import). Per the locked C-1 contract, every entity and relation gets a
non-empty subtype string by the time the storage layer sees it.
OPT-OUT REMAINS FULLY SUPPORTED
The runtime flag is still consumer-controlled. Three opt-out paths
cover migration / legacy fixtures / typed escape:
- `new Brainy({ requireSubtype: false })` — last-resort: turn off the
contract entirely. Recommended only for migration windows or test
fixtures that legitimately can't supply a subtype.
- `new Brainy({ requireSubtype: { except: [NounType.Thing, ...] } })` —
per-type allowlist: strict everywhere except the listed types.
- `brain.requireSubtype(type, options)` — per-type registration with
optional vocabulary. Composes with the brain-wide flag.
Default is now `true`. Opt-out is explicit and documented; nothing
silently degrades.
TEST SWEEP
Bulk-applied `requireSubtype: false` to every `new Brainy({...})` call
site across 120 test files. Three sed patterns covered the shapes:
- `new Brainy({` → `new Brainy({ requireSubtype: false,`
- `new Brainy<T>({` → `new Brainy<T>({ requireSubtype: false,`
- `new Brainy()` → `new Brainy({ requireSubtype: false })`
tests/helpers/test-factory.ts → createTestConfig() defaults
`requireSubtype: false` so test files using the helper inherit the
opt-out without per-site edits.
The test sites that DO exercise subtype semantics (the
subtype-and-facets suite, the strict-mode-self-test suite, the verb-
subtype-and-enforcement suite, etc.) already pass real subtypes — they
were the 7.30.x acceptance tests for this contract. Those tests
continue to pass unchanged.
CHANGES
src/brainy.ts
- normalizeConfig() — `requireSubtype` default `false` → `true`.
Comment refreshed to document the three opt-out paths.
tests/* (120 files)
- Bulk-edited brain construction sites. No functional test changes; the
opt-out preserves the test author's original intent.
tests/helpers/test-factory.ts
- createTestConfig() base config gains `requireSubtype: false`.
NO-OP for consumers who were already passing subtype on every write.
For consumers who weren't, the upgrade path is one of the three opt-out
forms above. Migration recipe documented in 8.0 release notes (next
commit).
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1408 / 1409 (same pre-existing race-condition outstanding;
no other regressions from the flip)
2026-06-09 14:58:25 -07:00
brain = new Brainy ( { requireSubtype : false ,
2025-10-24 12:59:41 -07:00
storage : {
type : 'filesystem' ,
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0
8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":
- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
/ `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
(`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.
Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
path : testDir
2025-10-24 12:59:41 -07:00
}
} )
await brain . init ( )
} )
2026-06-17 13:11:41 -07:00
afterAll ( async ( ) = > {
// Close the brain so background flush / writer-lock heartbeat stop before
// the dir is removed (prevents teardown timeouts and cross-test bleed).
await brain . close ( )
2025-10-24 12:59:41 -07:00
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true , force : true } )
}
} )
describe ( 'brain.updateMany()' , ( ) = > {
it ( 'should batch update multiple entities' , async ( ) = > {
console . log ( '\n📋 Test: brain.updateMany()' )
// Create test entities
const ids = await brain . addMany ( {
items : [
{ data : 'Entity 1' , type : NounType . Document , metadata : { status : 'draft' } } ,
{ data : 'Entity 2' , type : NounType . Document , metadata : { status : 'draft' } } ,
{ data : 'Entity 3' , type : NounType . Document , metadata : { status : 'draft' } }
]
} )
console . log ( ` Created ${ ids . successful . length } entities ` )
// Batch update all to published
await brain . updateMany ( {
items : ids.successful.map ( id = > ( {
id ,
feat(8.0): reserved-field enforcement — reservedFieldPolicy defaults to throw
An untyped (JS) caller that smuggles a Brainy-reserved field (confidence,
weight, subtype, visibility, service, createdBy, noun/verb, data, createdAt,
updatedAt, _rev) inside a write-path metadata bag previously got a silent
remap-or-drop — a class of bug where confidence-evolution writes no-oped for
weeks before being caught on read-back. 8.0 closes this with no silent failures.
- New BrainyConfig.reservedFieldPolicy: 'throw' | 'warn' | 'remap' (default 'throw').
'throw' rejects the write naming every offending key + its correct write path;
'warn' remaps with a one-shot per-key warning; 'remap' is the legacy silent path.
- Central enforceReservedPolicy gate wired into all four remap methods (add,
update, relate, updateRelation) so live calls AND their transact()/with()
mirrors honor it. Single-source reservedWritePath guidance shared by throw+warn.
- 'warn' now warns for EVERY reserved key (closes the gap where only
system-managed fields warned). Dead warnDropped* helpers removed.
- Import pipeline migrated to route reserved values (confidence/weight/subtype)
through dedicated params and strip reserved keys from extractor/customMetadata
bags via the canonical split*MetadataRecord helpers — imports no longer trip
the default throw.
- Tests: new reservedFieldPolicy matrix (throw/warn/remap across every write
path + transact); remap-correctness suite reframed as opt-in 'remap'; shared
test-factory no longer emits reserved keys in custom metadata.
2026-06-20 15:37:21 -07:00
// `updatedAt` is system-managed (set automatically on every write) —
// only the custom `status` field belongs in the metadata bag.
metadata : { status : 'published' }
2025-10-24 12:59:41 -07:00
} ) )
} )
// Verify all updated (status changed from 'draft' to 'published')
for ( const id of ids . successful ) {
const entity = await brain . get ( id )
expect ( entity ? . metadata ? . status ) . toBe ( 'published' )
}
console . log ( ` ✅ brain.updateMany() updated ${ ids . successful . length } entities ` )
} )
it ( 'should handle partial failures gracefully' , async ( ) = > {
console . log ( '\n📋 Test: brain.updateMany() error handling' )
const validId = await brain . add ( {
data : 'Valid entity' ,
type : NounType . Document
} )
// Mix valid and invalid IDs
await brain . updateMany ( {
items : [
{ id : validId , metadata : { updated : true } } ,
{ id : 'invalid-id-123' , metadata : { updated : true } }
]
} )
// Valid entity should be updated
const entity = await brain . get ( validId )
expect ( entity ? . metadata ? . updated ) . toBe ( true )
console . log ( ` ✅ brain.updateMany() handled partial failures ` )
} )
} )
describe ( 'brain.import() with VFS' , ( ) = > {
it ( 'should import CSV and create VFS entities when vfsPath specified' , async ( ) = > {
console . log ( '\n📋 Test: brain.import() with VFS' )
// Create test CSV file
const csvPath = path . join ( testDir , 'test-data.csv' )
const csvContent = ` name,age,role
Alice , 30 , Engineer
Bob , 25 , Designer
Carol , 35 , Manager `
fs . writeFileSync ( csvPath , csvContent )
// Import with VFS path
const result = await brain . import ( csvPath , {
vfsPath : '/imports/test-data.csv' ,
createEntities : true
} )
console . log ( ` Imported ${ result . stats . graphNodesCreated } entities ` )
console . log ( ` VFS files: ${ result . stats . vfsFilesCreated } ` )
expect ( result . stats . graphNodesCreated ) . toBeGreaterThan ( 0 )
expect ( result . stats . vfsFilesCreated ) . toBeGreaterThan ( 0 )
// Verify VFS file was created
2025-11-02 11:38:12 -08:00
const vfs = brain . vfs
2025-10-24 12:59:41 -07:00
await vfs . init ( )
const exists = await vfs . exists ( '/imports/test-data.csv' )
expect ( exists ) . toBe ( true )
// Verify entities were created (should exclude VFS by default)
// Note: Import creates entities based on CSV content
const allEntities = await brain . find ( {
limit : 100
} )
// Filter out VFS entities
const knowledgeEntities = allEntities . filter ( e = > e . metadata ? . isVFS !== true )
console . log ( ` Total entities: ${ allEntities . length } , Knowledge: ${ knowledgeEntities . length } ` )
// Should have created at least one knowledge entity from CSV
expect ( knowledgeEntities . length ) . toBeGreaterThan ( 0 )
console . log ( ` ✅ brain.import() created VFS file and entities ` )
} )
it ( 'should import without VFS when vfsPath not specified' , async ( ) = > {
console . log ( '\n📋 Test: brain.import() without VFS' )
// Create test CSV
const csvPath = path . join ( testDir , 'no-vfs.csv' )
const csvContent = ` product,price
Widget , 10
Gadget , 20 `
fs . writeFileSync ( csvPath , csvContent )
// Import without VFS path
const result = await brain . import ( csvPath , {
createEntities : true
} )
console . log ( ` Imported ${ result . stats . graphNodesCreated } entities ` )
console . log ( ` VFS files: ${ result . stats . vfsFilesCreated || 0 } ` )
expect ( result . stats . graphNodesCreated ) . toBeGreaterThan ( 0 )
// Note: Import always creates VFS files (uses default path if not specified)
expect ( result . stats . vfsFilesCreated ) . toBeGreaterThan ( 0 )
console . log ( ` ✅ brain.import() worked without VFS ` )
} )
} )
describe ( 'VFS File Operations' , ( ) = > {
let vfs : any
beforeAll ( async ( ) = > {
2025-11-02 11:38:12 -08:00
vfs = brain . vfs
2025-10-24 12:59:41 -07:00
await vfs . init ( )
} )
it ( 'vfs.unlink() - should delete files' , async ( ) = > {
console . log ( '\n📋 Test: vfs.unlink()' )
await vfs . writeFile ( '/test-unlink.txt' , 'Delete me' )
expect ( await vfs . exists ( '/test-unlink.txt' ) ) . toBe ( true )
await vfs . unlink ( '/test-unlink.txt' )
expect ( await vfs . exists ( '/test-unlink.txt' ) ) . toBe ( false )
console . log ( ` ✅ vfs.unlink() deleted file ` )
} )
it ( 'vfs.rmdir() - should remove directories' , async ( ) = > {
console . log ( '\n📋 Test: vfs.rmdir()' )
await vfs . mkdir ( '/test-dir-delete' , { recursive : true } )
expect ( await vfs . exists ( '/test-dir-delete' ) ) . toBe ( true )
await vfs . rmdir ( '/test-dir-delete' )
expect ( await vfs . exists ( '/test-dir-delete' ) ) . toBe ( false )
console . log ( ` ✅ vfs.rmdir() removed directory ` )
} )
it ( 'vfs.rmdir() - should remove directories recursively' , async ( ) = > {
console . log ( '\n📋 Test: vfs.rmdir() recursive' )
await vfs . mkdir ( '/nested/deep/dir' , { recursive : true } )
await vfs . writeFile ( '/nested/deep/dir/file.txt' , 'content' )
await vfs . rmdir ( '/nested' , { recursive : true } )
expect ( await vfs . exists ( '/nested' ) ) . toBe ( false )
console . log ( ` ✅ vfs.rmdir() removed directory recursively ` )
} )
it ( 'vfs.rename() - should rename files' , async ( ) = > {
console . log ( '\n📋 Test: vfs.rename()' )
await vfs . writeFile ( '/old-name.txt' , 'Content' )
await vfs . rename ( '/old-name.txt' , '/new-name.txt' )
expect ( await vfs . exists ( '/old-name.txt' ) ) . toBe ( false )
expect ( await vfs . exists ( '/new-name.txt' ) ) . toBe ( true )
const content = await vfs . readFile ( '/new-name.txt' )
expect ( content . toString ( ) ) . toBe ( 'Content' )
console . log ( ` ✅ vfs.rename() renamed file ` )
} )
it ( 'vfs.rename() - should rename directories' , async ( ) = > {
console . log ( '\n📋 Test: vfs.rename() directory' )
await vfs . mkdir ( '/old-dir' , { recursive : true } )
await vfs . writeFile ( '/old-dir/file.txt' , 'test' )
await vfs . rename ( '/old-dir' , '/new-dir' )
expect ( await vfs . exists ( '/old-dir' ) ) . toBe ( false )
expect ( await vfs . exists ( '/new-dir' ) ) . toBe ( true )
expect ( await vfs . exists ( '/new-dir/file.txt' ) ) . toBe ( true )
console . log ( ` ✅ vfs.rename() renamed directory ` )
} )
it ( 'vfs.copy() - should copy files' , async ( ) = > {
console . log ( '\n📋 Test: vfs.copy()' )
await vfs . writeFile ( '/source.txt' , 'Original content' )
await vfs . copy ( '/source.txt' , '/destination.txt' )
expect ( await vfs . exists ( '/source.txt' ) ) . toBe ( true )
expect ( await vfs . exists ( '/destination.txt' ) ) . toBe ( true )
const content = await vfs . readFile ( '/destination.txt' )
expect ( content . toString ( ) ) . toBe ( 'Original content' )
console . log ( ` ✅ vfs.copy() copied file ` )
} )
it ( 'vfs.copy() - should copy directories recursively' , async ( ) = > {
console . log ( '\n📋 Test: vfs.copy() directory' )
await vfs . mkdir ( '/copy-source' , { recursive : true } )
await vfs . writeFile ( '/copy-source/file1.txt' , 'content1' )
await vfs . writeFile ( '/copy-source/file2.txt' , 'content2' )
await vfs . copy ( '/copy-source' , '/copy-dest' , { recursive : true } )
expect ( await vfs . exists ( '/copy-source' ) ) . toBe ( true )
expect ( await vfs . exists ( '/copy-dest' ) ) . toBe ( true )
expect ( await vfs . exists ( '/copy-dest/file1.txt' ) ) . toBe ( true )
expect ( await vfs . exists ( '/copy-dest/file2.txt' ) ) . toBe ( true )
console . log ( ` ✅ vfs.copy() copied directory recursively ` )
} )
it ( 'vfs.move() - should move files' , async ( ) = > {
console . log ( '\n📋 Test: vfs.move()' )
await vfs . writeFile ( '/move-source.txt' , 'Move me' )
await vfs . move ( '/move-source.txt' , '/move-dest.txt' )
expect ( await vfs . exists ( '/move-source.txt' ) ) . toBe ( false )
expect ( await vfs . exists ( '/move-dest.txt' ) ) . toBe ( true )
const content = await vfs . readFile ( '/move-dest.txt' )
expect ( content . toString ( ) ) . toBe ( 'Move me' )
console . log ( ` ✅ vfs.move() moved file ` )
} )
it ( 'VFS operations preserve isVFS flag' , async ( ) = > {
console . log ( '\n📋 Test: VFS operations preserve metadata' )
await vfs . writeFile ( '/vfs-test.txt' , 'test' )
// Verify entity has isVFS flag
const entities = await brain . find ( {
where : { path : '/vfs-test.txt' } ,
includeVFS : true ,
limit : 1
} )
expect ( entities . length ) . toBe ( 1 )
expect ( entities [ 0 ] . metadata ? . isVFS ) . toBe ( true )
expect ( entities [ 0 ] . metadata ? . vfsType ) . toBe ( 'file' )
console . log ( ` ✅ VFS operations preserve isVFS metadata ` )
} )
} )
describe ( 'Production Quality Verification' , ( ) = > {
it ( 'should handle large batch updates efficiently' , async ( ) = > {
console . log ( '\n📋 Test: Large batch updateMany()' )
const start = Date . now ( )
// Create 50 entities
const ids = await brain . addMany ( {
items : Array ( 50 ) . fill ( null ) . map ( ( _ , i ) = > ( {
data : ` Batch update entity ${ i } ` ,
type : NounType . Document ,
metadata : { version : 1 }
} ) )
} )
// Batch update all
await brain . updateMany ( {
items : ids.successful.map ( id = > ( {
id ,
metadata : { version : 2 , updated : true }
} ) )
} )
const time = Date . now ( ) - start
// Verify all updated
const sample = await brain . get ( ids . successful [ 0 ] )
expect ( sample ? . metadata ? . version ) . toBe ( 2 )
expect ( sample ? . metadata ? . updated ) . toBe ( true )
expect ( time ) . toBeLessThan ( 10000 ) // < 10 seconds for 50 updates
console . log ( ` ✅ Updated 50 entities in ${ time } ms ` )
} )
it ( 'should handle VFS file operations at scale' , async ( ) = > {
console . log ( '\n📋 Test: VFS operations at scale' )
2025-11-02 11:38:12 -08:00
const vfs = brain . vfs
2025-10-24 12:59:41 -07:00
const start = Date . now ( )
// Create 20 files
for ( let i = 0 ; i < 20 ; i ++ ) {
await vfs . writeFile ( ` /scale-test/file ${ i } .txt ` , ` Content ${ i } ` )
}
// Copy all
await vfs . copy ( '/scale-test' , '/scale-test-copy' , { recursive : true } )
// Verify
const entries = await vfs . readdir ( '/scale-test-copy' )
const time = Date . now ( ) - start
expect ( entries . length ) . toBe ( 20 )
expect ( time ) . toBeLessThan ( 5000 ) // < 5 seconds
console . log ( ` ✅ Created and copied 20 files in ${ time } ms ` )
} )
} )
} )