2025-09-11 16:23:32 -07:00
/ * *
* Unit tests for Brainy . update ( ) method
* Tests all aspects of updating entities in the neural database
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import {
createAddParams ,
createTestConfig ,
} from '../../helpers/test-factory'
import {
assertCompletesWithin ,
} from '../../helpers/test-assertions'
describe ( 'Brainy.update()' , ( ) = > {
let brain : Brainy
beforeEach ( async ( ) = > {
brain = new Brainy ( createTestConfig ( ) )
await brain . init ( )
} )
afterEach ( async ( ) = > {
await brain . close ( )
} )
describe ( 'success paths' , ( ) = > {
it ( 'should update entity metadata' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Original content' ,
type : 'document' ,
metadata : { version : 1 , status : 'draft' }
} ) )
// Act
await brain . update ( {
id ,
metadata : { version : 2 , status : 'published' } ,
merge : false
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . metadata . version ) . toBe ( 2 )
expect ( updated ! . metadata . status ) . toBe ( 'published' )
} )
it ( 'should merge metadata when merge is true' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test content' ,
type : 'thing' ,
metadata : {
name : 'Original' ,
count : 10 ,
tags : [ 'original' ]
}
} ) )
// Act
await brain . update ( {
id ,
metadata : {
count : 20 ,
tags : [ 'updated' ] ,
newField : 'added'
} ,
merge : true
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . metadata . name ) . toBe ( 'Original' ) // Preserved
expect ( updated ! . metadata . count ) . toBe ( 20 ) // Updated
expect ( updated ! . metadata . tags ) . toEqual ( [ 'updated' ] ) // Replaced
expect ( updated ! . metadata . newField ) . toBe ( 'added' ) // Added
} )
it ( 'should replace metadata when merge is false' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test content' ,
type : 'thing' ,
metadata : {
name : 'Original' ,
count : 10 ,
willBeRemoved : true
}
} ) )
// Act
await brain . update ( {
id ,
metadata : {
newData : 'replaced' ,
count : 99
} ,
merge : false
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . metadata . newData ) . toBe ( 'replaced' )
expect ( updated ! . metadata . count ) . toBe ( 99 )
expect ( updated ! . metadata . name ) . toBeUndefined ( ) // Removed
expect ( updated ! . metadata . willBeRemoved ) . toBeUndefined ( ) // Removed
} )
it ( 'should update entity type' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Versatile content' ,
type : 'thing' ,
metadata : { original : true }
} ) )
// Act
await brain . update ( {
id ,
type : 'document'
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . type ) . toBe ( 'document' )
expect ( updated ! . metadata . original ) . toBe ( true ) // Metadata preserved
} )
it ( 'should update entity vector when data changes' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Original text content' ,
type : 'thing'
} ) )
const original = await brain . get ( id )
const originalVector = original ! . vector
// Act - Update with new data triggers re-embedding
await brain . update ( {
id ,
data : 'Completely different text content'
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
// Vector should be different after re-embedding
expect ( updated ! . vector ) . not . toEqual ( originalVector )
expect ( updated ! . vector . length ) . toBe ( originalVector . length )
} )
it ( 'should re-embed when data is updated' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Original text' ,
type : 'document'
} ) )
const original = await brain . get ( id )
// Act
await brain . update ( {
id ,
data : 'Completely different text'
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
// Vector should be different after re-embedding
expect ( updated ! . vector ) . not . toEqual ( original ! . vector )
} )
it ( 'should update timestamps' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Timestamp test' ,
type : 'thing'
} ) )
const original = await brain . get ( id )
const originalUpdatedAt = original ! . updatedAt || original ! . createdAt
// Wait a bit to ensure timestamp difference
await new Promise ( resolve = > setTimeout ( resolve , 10 ) )
// Act
await brain . update ( {
id ,
metadata : { updated : true }
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . createdAt ) . toBe ( original ! . createdAt ) // Created stays same
expect ( updated ! . updatedAt ) . toBeGreaterThan ( originalUpdatedAt )
} )
it ( 'should handle multiple updates to same entity' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Multi-update test' ,
type : 'thing' ,
metadata : { version : 1 }
} ) )
// Act - Multiple sequential updates
await brain . update ( { id , metadata : { version : 2 } , merge : true } )
await brain . update ( { id , metadata : { version : 3 } , merge : true } )
await brain . update ( { id , metadata : { version : 4 } , merge : true } )
// Assert
const final = await brain . get ( id )
expect ( final ) . not . toBeNull ( )
expect ( final ! . metadata . version ) . toBe ( 4 )
} )
} )
describe ( 'error paths' , ( ) = > {
it ( 'should handle updating non-existent entity' , async ( ) = > {
// Arrange
const fakeId = 'non-existent-12345'
// Act & Assert
await expect ( brain . update ( {
id : fakeId ,
metadata : { test : 'value' }
} ) ) . rejects . toThrow ( )
} )
2025-09-12 14:37:39 -07:00
it ( 'should reject invalid entity type on update' , async ( ) = > {
2025-09-11 16:23:32 -07:00
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test' ,
type : 'thing'
} ) )
2025-09-12 14:37:39 -07:00
// Act & Assert - Should properly validate type
await expect ( brain . update ( {
2025-09-11 16:23:32 -07:00
id ,
type : 'invalid_type' as any
2025-09-12 14:37:39 -07:00
} ) ) . rejects . toThrow ( 'invalid NounType' )
2025-09-11 16:23:32 -07:00
} )
it ( 'should not update vector directly via update method' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test' ,
type : 'thing'
} ) )
const original = await brain . get ( id )
const originalVector = original ! . vector
// Create a properly dimensioned but different vector
const differentVector = originalVector . map ( v = > v * 2 )
// Act - Update with vector param (not supported)
await brain . update ( {
id ,
vector : differentVector
} )
// Assert - Vector should not change (update ignores vector param)
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . vector ) . toEqual ( originalVector )
} )
2025-09-12 14:37:39 -07:00
it ( 'should reject empty update parameters' , async ( ) = > {
2025-09-11 16:23:32 -07:00
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test' ,
type : 'thing' ,
metadata : { original : true }
} ) )
2025-09-12 14:37:39 -07:00
// Act & Assert - Should require at least one field to update
await expect ( brain . update ( { id } ) ) . rejects . toThrow ( 'must specify at least one field to update' )
2025-09-11 16:23:32 -07:00
} )
} )
describe ( 'edge cases' , ( ) = > {
2025-09-12 14:37:39 -07:00
it ( 'should reject updating with null metadata' , async ( ) = > {
2025-09-11 16:23:32 -07:00
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Test' ,
type : 'thing' ,
metadata : { existing : 'data' , another : 'field' }
} ) )
2025-09-12 14:37:39 -07:00
// Act & Assert - null metadata is not a valid update
// This prevents accidental data loss from null values
await expect ( brain . update ( {
2025-09-11 16:23:32 -07:00
id ,
metadata : null as any ,
merge : false
2025-09-12 14:37:39 -07:00
} ) ) . rejects . toThrow ( 'must specify at least one field to update' )
2025-09-11 16:23:32 -07:00
2025-09-12 14:37:39 -07:00
// Verify original data is untouched
const entity = await brain . get ( id )
expect ( entity ! . metadata . existing ) . toBe ( 'data' )
expect ( entity ! . metadata . another ) . toBe ( 'field' )
2025-09-11 16:23:32 -07:00
} )
it ( 'should handle concurrent updates' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Concurrent test' ,
type : 'thing' ,
metadata : { counter : 0 }
} ) )
// Act - Fire 10 concurrent updates
const updates = Array . from ( { length : 10 } , ( _ , i ) = >
brain . update ( {
id ,
metadata : { counter : i + 1 } ,
merge : false
} )
)
await Promise . all ( updates )
// Assert - Last update wins
const final = await brain . get ( id )
expect ( final ) . not . toBeNull ( )
expect ( final ! . metadata . counter ) . toBeGreaterThan ( 0 )
expect ( final ! . metadata . counter ) . toBeLessThanOrEqual ( 10 )
} )
it ( 'should handle very large metadata updates' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Large metadata test' ,
type : 'thing'
} ) )
const largeMetadata = {
bigArray : new Array ( 1000 ) . fill ( 'item' ) ,
bigObject : Object.fromEntries (
Array . from ( { length : 100 } , ( _ , i ) = > [ ` key ${ i } ` , ` value ${ i } ` ] )
) ,
deepNesting : Array ( 10 ) . fill ( null ) . reduce (
( acc ) = > ( { nested : acc } ) ,
{ value : 'deep' }
)
}
// Act
await brain . update ( {
id ,
metadata : largeMetadata ,
merge : false
} )
// Assert
const updated = await brain . get ( id )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . metadata . bigArray ) . toHaveLength ( 1000 )
expect ( Object . keys ( updated ! . metadata . bigObject ) ) . toHaveLength ( 100 )
} )
it ( 'should preserve entity ID during update' , async ( ) = > {
// Arrange
const customId = 'preserve-me-123'
await brain . add ( createAddParams ( {
id : customId ,
data : 'Test' ,
type : 'thing'
} ) )
// Act
await brain . update ( {
id : customId ,
data : 'Updated content' ,
type : 'document' ,
metadata : { changed : true }
} )
// Assert
const updated = await brain . get ( customId )
expect ( updated ) . not . toBeNull ( )
expect ( updated ! . id ) . toBe ( customId )
expect ( updated ! . type ) . toBe ( 'document' )
expect ( updated ! . metadata . changed ) . toBe ( true )
} )
} )
describe ( 'performance' , ( ) = > {
it ( 'should update entities quickly' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Performance test' ,
type : 'thing'
} ) )
// Act & Assert
await assertCompletesWithin (
( ) = > brain . update ( {
id ,
metadata : { updated : true }
} ) ,
100 , // Should complete within 100ms
'Update operation'
)
} )
it ( 'should handle batch updates efficiently' , async ( ) = > {
// Arrange - Create 100 entities
const ids : string [ ] = [ ]
for ( let i = 0 ; i < 100 ; i ++ ) {
const id = await brain . add ( createAddParams ( {
data : ` Entity ${ i } ` ,
type : 'thing' ,
metadata : { index : i }
} ) )
ids . push ( id )
}
// Act - Update all entities
const start = performance . now ( )
const updates = ids . map ( ( id , i ) = >
brain . update ( {
id ,
metadata : { index : i , updated : true } ,
merge : true
} )
)
await Promise . all ( updates )
const duration = performance . now ( ) - start
// Assert
const opsPerSecond = ( 100 / duration ) * 1000
expect ( opsPerSecond ) . toBeGreaterThan ( 100 ) // At least 100 updates/second
// Verify updates
const entity = await brain . get ( ids [ 0 ] )
expect ( entity ! . metadata . updated ) . toBe ( true )
} )
} )
describe ( 'consistency' , ( ) = > {
it ( 'should maintain consistency after update' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Consistency test' ,
type : 'thing' ,
metadata : { important : 'data' , version : 1 }
} ) )
// Act
await brain . update ( {
id ,
metadata : { version : 2 } ,
merge : true
} )
// Assert - Multiple gets should return same updated data
const get1 = await brain . get ( id )
const get2 = await brain . get ( id )
expect ( get1 ) . not . toBeNull ( )
expect ( get2 ) . not . toBeNull ( )
expect ( get1 ! . metadata . version ) . toBe ( 2 )
expect ( get2 ! . metadata . version ) . toBe ( 2 )
expect ( get1 ! . metadata . important ) . toBe ( 'data' ) // Preserved
expect ( get2 ! . metadata . important ) . toBe ( 'data' ) // Preserved
} )
it ( 'should reflect updates in vector search' , async ( ) = > {
// Arrange
const id = await brain . add ( createAddParams ( {
data : 'Original searchable content' ,
type : 'document' ,
metadata : { category : 'original' }
} ) )
// Act
await brain . update ( {
id ,
data : 'Updated searchable content' ,
metadata : { category : 'updated' } ,
merge : false
} )
// Assert - Vector search should find the updated entity
const results = await brain . find ( {
query : 'Updated searchable content' ,
limit : 10
} )
const found = results . find ( r = > r . id === id )
expect ( found ) . toBeDefined ( )
expect ( found ! . entity . metadata . category ) . toBe ( 'updated' )
} )
} )
} )