2025-09-11 16:23:32 -07:00
import { describe , it , expect , beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { createAddParams } from '../../helpers/test-factory'
import { NounType } from '../../../src/types/graphTypes'
describe ( 'Brainy.find()' , ( ) = > {
let brain : Brainy < any >
2025-09-22 15:45:35 -07:00
2025-09-11 16:23:32 -07:00
beforeEach ( async ( ) = > {
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-09-22 15:45:35 -07:00
storage : { type : 'memory' } // Use memory storage for isolation
} )
2025-09-11 16:23:32 -07:00
await brain . init ( )
} )
describe ( 'success paths' , ( ) = > {
it ( 'should find entities by text query' , async ( ) = > {
// Arrange
const entity1 = await brain . add ( createAddParams ( {
data : 'JavaScript programming language' ,
metadata : { category : 'tech' }
} ) )
const entity2 = await brain . add ( createAddParams ( {
data : 'Python programming language' ,
metadata : { category : 'tech' }
} ) )
await brain . add ( createAddParams ( {
data : 'Coffee brewing techniques' ,
metadata : { category : 'food' }
} ) )
// Act
const results = await brain . find ( {
query : 'programming language' ,
limit : 10
} )
// Assert
expect ( results . length ) . toBeGreaterThanOrEqual ( 2 )
const ids = results . map ( r = > r . entity . id )
expect ( ids ) . toContain ( entity1 )
expect ( ids ) . toContain ( entity2 )
} )
it ( 'should find entities with metadata filter' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( {
data : 'Entity 1' ,
metadata : { category : 'A' , status : 'active' }
} ) )
const entity2 = await brain . add ( createAddParams ( {
data : 'Entity 2' ,
metadata : { category : 'B' , status : 'active' }
} ) )
const entity3 = await brain . add ( createAddParams ( {
data : 'Entity 3' ,
metadata : { category : 'B' , status : 'inactive' }
} ) )
// Act
const results = await brain . find ( {
query : 'Entity' ,
where : { category : 'B' } ,
limit : 10
} )
// Assert
expect ( results . length ) . toBe ( 2 )
const ids = results . map ( r = > r . entity . id )
expect ( ids ) . toContain ( entity2 )
expect ( ids ) . toContain ( entity3 )
} )
it ( 'should respect limit parameter' , async ( ) = > {
// Arrange - Add many entities
await Promise . all (
Array . from ( { length : 20 } , ( _ , i ) = >
brain . add ( createAddParams ( {
data : ` Test entity number ${ i } ` ,
metadata : { index : i }
} ) )
)
)
// Act
const results = await brain . find ( {
query : 'Test entity' ,
limit : 5
} )
// Assert
expect ( results . length ) . toBe ( 5 )
} )
it ( 'should respect offset parameter for pagination' , async ( ) = > {
// Arrange
const ids = await Promise . all (
Array . from ( { length : 10 } , ( _ , i ) = >
brain . add ( createAddParams ( {
data : ` Same content ` ,
metadata : { order : i }
} ) )
)
)
// Act
const page1 = await brain . find ( {
query : 'Same content' ,
limit : 5 ,
offset : 0
} )
const page2 = await brain . find ( {
query : 'Same content' ,
limit : 5 ,
offset : 5
} )
// Assert
expect ( page1 . length ) . toBe ( 5 )
expect ( page2 . length ) . toBe ( 5 )
// Pages should have different entities
const page1Ids = page1 . map ( r = > r . entity . id )
const page2Ids = page2 . map ( r = > r . entity . id )
const overlap = page1Ids . filter ( id = > page2Ids . includes ( id ) )
expect ( overlap . length ) . toBe ( 0 )
} )
it ( 'should include relevance scores' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( {
data : 'Exact match: machine learning' ,
metadata : { exact : true }
} ) )
await brain . add ( createAddParams ( {
data : 'Partial: machine' ,
metadata : { partial : true }
} ) )
await brain . add ( createAddParams ( {
data : 'Unrelated: cooking recipes' ,
metadata : { unrelated : true }
} ) )
// Act
const results = await brain . find ( {
query : 'machine learning' ,
limit : 10
} )
// Assert
expect ( results . length ) . toBeGreaterThan ( 0 )
results . forEach ( r = > {
expect ( r . score ) . toBeDefined ( )
expect ( r . score ) . toBeGreaterThanOrEqual ( 0 )
expect ( r . score ) . toBeLessThanOrEqual ( 1 )
} )
// Higher relevance should have higher scores
if ( results . length > 1 ) {
expect ( results [ 0 ] . score ) . toBeGreaterThanOrEqual ( results [ 1 ] . score )
}
} )
it ( 'should filter by entity type' , async ( ) = > {
// Arrange
const personId = await brain . add ( createAddParams ( {
data : 'John Doe' ,
type : NounType . Person ,
metadata : { role : 'developer' }
} ) )
const placeId = await brain . add ( createAddParams ( {
data : 'New York City' ,
type : NounType . Location ,
metadata : { country : 'USA' }
} ) )
await brain . add ( createAddParams ( {
data : 'Apple Inc' ,
type : NounType . Organization ,
metadata : { industry : 'tech' }
} ) )
// Act
const peopleResults = await brain . find ( {
query : '' ,
type : NounType . Person ,
limit : 10
} )
const placeResults = await brain . find ( {
query : '' ,
type : NounType . Location ,
limit : 10
} )
// Assert
expect ( peopleResults . some ( r = > r . entity . id === personId ) ) . toBe ( true )
expect ( peopleResults . some ( r = > r . entity . id === placeId ) ) . toBe ( false )
expect ( placeResults . some ( r = > r . entity . id === placeId ) ) . toBe ( true )
expect ( placeResults . some ( r = > r . entity . id === personId ) ) . toBe ( false )
} )
it ( 'should find by multiple types' , async ( ) = > {
// Arrange
const personId = await brain . add ( createAddParams ( {
data : 'Alice' ,
type : NounType . Person
} ) )
const eventId = await brain . add ( createAddParams ( {
data : 'Conference' ,
type : NounType . Event
} ) )
await brain . add ( createAddParams ( {
data : 'Document' ,
type : NounType . Document
} ) )
// Act
const results = await brain . find ( {
query : '' ,
type : [ NounType . Person , NounType . Event ] ,
limit : 10
} )
// Assert
const ids = results . map ( r = > r . entity . id )
expect ( ids ) . toContain ( personId )
expect ( ids ) . toContain ( eventId )
// Should not contain other types
const types = results . map ( r = > r . entity . type )
expect ( types . every ( t = > t === NounType . Person || t === NounType . Event ) ) . toBe ( true )
} )
} )
describe ( 'error paths' , ( ) = > {
it ( 'should handle empty query' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( { data : 'Test entity' } ) )
// Act
const results = await brain . find ( {
query : '' ,
limit : 10
} )
// Assert - Should return all entities
expect ( results . length ) . toBeGreaterThan ( 0 )
} )
it ( 'should handle query with no matches' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( { data : 'Apple' } ) )
await brain . add ( createAddParams ( { data : 'Banana' } ) )
// Act
const results = await brain . find ( {
query : 'Xylophones and Zebras' ,
limit : 10
} )
// Assert - May return results with low scores
expect ( Array . isArray ( results ) ) . toBe ( true )
} )
it ( 'should handle invalid parameters' , async ( ) = > {
// Act & Assert
await expect ( brain . find ( {
query : 'test' ,
limit : - 1
} as any ) ) . rejects . toThrow ( )
await expect ( brain . find ( {
query : 'test' ,
offset : - 1
} as any ) ) . rejects . toThrow ( )
} )
} )
describe ( 'edge cases' , ( ) = > {
it ( 'should handle very long queries' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( { data : 'Short text' } ) )
const longQuery = 'Lorem ipsum ' . repeat ( 100 )
// Act - Should not throw
const results = await brain . find ( {
query : longQuery ,
limit : 10
} )
// Assert
expect ( Array . isArray ( results ) ) . toBe ( true )
} )
it ( 'should handle special characters in query' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( {
data : 'Email: user@example.com'
} ) )
// Act
const results = await brain . find ( {
query : 'user@example.com' ,
limit : 10
} )
// Assert
expect ( results . length ) . toBeGreaterThan ( 0 )
} )
it ( 'should handle unicode in queries' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : '你好世界 Hello World'
} ) )
// Act
const results = await brain . find ( {
query : '你好' ,
limit : 10
} )
// Assert
expect ( results . some ( r = > r . entity . id === id ) ) . toBe ( true )
} )
it ( 'should handle concurrent searches' , async ( ) = > {
// Arrange
await Promise . all (
Array . from ( { length : 10 } , ( _ , i ) = >
brain . add ( createAddParams ( { data : ` Entity ${ i } ` } ) )
)
)
// Act - Run multiple searches concurrently
const searches = Array . from ( { length : 5 } , ( ) = >
brain . find ( {
query : 'Entity' ,
limit : 5
} )
)
const results = await Promise . all ( searches )
// Assert
results . forEach ( r = > {
expect ( r . length ) . toBeGreaterThan ( 0 )
expect ( r . length ) . toBeLessThanOrEqual ( 5 )
} )
} )
} )
describe ( 'performance' , ( ) = > {
it ( 'should search quickly' , async ( ) = > {
// Arrange
await brain . add ( createAddParams ( { data : 'Searchable content' } ) )
// Act
const start = Date . now ( )
await brain . find ( {
query : 'Searchable' ,
limit : 10
} )
const duration = Date . now ( ) - start
// Assert
expect ( duration ) . toBeLessThan ( 100 )
} )
it ( 'should handle large result sets efficiently' , async ( ) = > {
// Arrange - Add many entities
await Promise . all (
Array . from ( { length : 100 } , ( _ , i ) = >
brain . add ( createAddParams ( { data : ` Entity ${ i } ` } ) )
)
)
2025-09-12 14:37:39 -07:00
// Act - Use empty query to test pagination performance on large dataset
2025-09-11 16:23:32 -07:00
const start = Date . now ( )
const results = await brain . find ( {
limit : 50
} )
const duration = Date . now ( ) - start
// Assert
expect ( results . length ) . toBe ( 50 )
expect ( duration ) . toBeLessThan ( 500 )
} )
} )
describe ( 'integration' , ( ) = > {
it ( 'should work with complex filters' , async ( ) = > {
// Arrange
const target = await brain . add ( createAddParams ( {
data : 'Target entity' ,
type : NounType . Document ,
metadata : {
status : 'published' ,
category : 'tech' ,
priority : 'high'
}
} ) )
await brain . add ( createAddParams ( {
data : 'Other entity' ,
type : NounType . Document ,
metadata : {
status : 'draft' ,
category : 'tech' ,
priority : 'high'
}
} ) )
// Act
const results = await brain . find ( {
query : '' ,
type : NounType . Document ,
where : {
status : 'published' ,
category : 'tech'
} ,
limit : 10
} )
// Assert
expect ( results . some ( r = > r . entity . id === target ) ) . toBe ( true )
2025-10-09 16:58:01 -07:00
expect ( results . every ( r = >
2025-09-11 16:23:32 -07:00
r . entity . metadata . status === 'published' &&
r . entity . metadata . category === 'tech'
) ) . toBe ( true )
} )
} )
} )