2025-08-25 09:52:32 -07:00
/ * *
* Tests for distributed caching behavior with shared storage
* /
import { describe , it , expect , beforeEach , afterEach , vi } from 'vitest'
import { BrainyData } from '../src/brainyData.js'
import { cleanupWorkerPools } from '../src/utils/index.js'
describe ( 'Distributed Caching' , ( ) = > {
let serviceA : BrainyData
let serviceB : BrainyData
beforeEach ( async ( ) = > {
// Mock the checkForUpdates to simulate real-time updates without waiting
const mockCheckForUpdates = vi . fn ( )
// Create two services sharing the same storage configuration
const sharedConfig = {
storage : {
forceMemoryStorage : true // Use memory storage for testing
} ,
searchCache : {
enabled : true ,
maxSize : 50 ,
maxAge : 60000 // 1 minute
} ,
realtimeUpdates : {
enabled : true ,
interval : 1000 , // 1 second for testing
updateIndex : true ,
updateStatistics : true
} ,
logging : {
verbose : false
}
}
serviceA = new BrainyData ( sharedConfig )
serviceB = new BrainyData ( sharedConfig )
await serviceA . init ( )
await serviceB . init ( )
} )
afterEach ( async ( ) = > {
2025-08-25 17:12:58 -07:00
await serviceA . clearAll ( { force : true } )
await serviceB . clearAll ( { force : true } )
2025-08-25 09:52:32 -07:00
await cleanupWorkerPools ( )
} )
describe ( 'Cache Invalidation on External Changes' , ( ) = > {
it ( 'should invalidate cache when external data changes are detected' , async ( ) = > {
// Since we're using memory storage and separate instances for testing,
// we'll simulate distributed behavior within a single service
// Add initial data
await serviceA . add ( {
id : 'item-1' ,
text : 'initial data from service A'
} )
// Search and cache the result
2025-08-26 12:03:45 -07:00
const results1 = await serviceA . search ( 'initial data' , { limit : 5 } )
2025-08-25 09:52:32 -07:00
expect ( results1 . length ) . toBe ( 1 )
// Verify cache is populated
let stats = serviceA . getCacheStats ( )
expect ( stats . search . size ) . toBe ( 1 )
// Add more data (simulates external changes)
await serviceA . add ( {
id : 'item-2' ,
text : 'new data from service A'
} )
// Cache should have been invalidated due to the add operation
stats = serviceA . getCacheStats ( )
expect ( stats . search . size ) . toBe ( 0 ) // Cache cleared
// Search again - should get fresh results including new data
2025-08-26 12:03:45 -07:00
const results2 = await serviceA . search ( 'data from service' , { limit : 10 } )
2025-08-25 09:52:32 -07:00
expect ( results2 . length ) . toBe ( 2 ) // Should now see both items
stats = serviceA . getCacheStats ( )
// Cache should be rebuilt with fresh data
expect ( stats . search . size ) . toBe ( 1 ) // New cache entry
} )
it ( 'should handle cache expiration gracefully' , async ( ) = > {
// Create a service with very short cache TTL
const shortCacheService = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
searchCache : {
enabled : true ,
maxAge : 100 // 100ms - very short for testing
} ,
logging : { verbose : false }
} )
await shortCacheService . init ( )
// Add data and search
await shortCacheService . add ( {
id : 'item-1' ,
text : 'short cache test'
} )
2025-08-26 12:03:45 -07:00
const results1 = await shortCacheService . search ( 'short cache' , { limit : 5 } )
2025-08-25 09:52:32 -07:00
expect ( results1 . length ) . toBe ( 1 )
// Wait for cache to expire
await new Promise ( resolve = > setTimeout ( resolve , 150 ) )
// Clean up expired entries
const expiredCount = shortCacheService [ 'searchCache' ] . cleanupExpiredEntries ( )
expect ( expiredCount ) . toBeGreaterThan ( 0 )
// Search again - should work fine with fresh data
2025-08-26 12:03:45 -07:00
const results2 = await shortCacheService . search ( 'short cache' , { limit : 5 } )
2025-08-25 09:52:32 -07:00
expect ( results2 . length ) . toBe ( 1 )
2025-08-25 17:12:58 -07:00
await shortCacheService . clearAll ( { force : true } )
2025-08-25 09:52:32 -07:00
} )
it ( 'should provide cache statistics for monitoring' , async ( ) = > {
// Add test data
for ( let i = 0 ; i < 10 ; i ++ ) {
await serviceA . add ( {
id : ` item- ${ i } ` ,
text : ` test data ${ i } `
} )
}
// Clear cache to start fresh
serviceA . clearCache ( )
// Perform searches to populate cache
2025-08-26 12:03:45 -07:00
await serviceA . search ( 'test data' , { limit : 5 } ) // Miss
await serviceA . search ( 'test data' , { limit : 5 } ) // Hit
await serviceA . search ( 'test data' , { limit : 3 } ) // Miss (different k)
await serviceA . search ( 'test data' , { limit : 3 } ) // Hit
2025-08-25 09:52:32 -07:00
const stats = serviceA . getCacheStats ( )
expect ( stats . search . hits ) . toBe ( 2 )
expect ( stats . search . misses ) . toBe ( 2 )
expect ( stats . search . hitRate ) . toBe ( 0.5 )
expect ( stats . search . size ) . toBe ( 2 ) // Two different cache entries
expect ( stats . searchMemoryUsage ) . toBeGreaterThan ( 0 )
} )
} )
describe ( 'Real-time Update Integration' , ( ) = > {
it ( 'should enable real-time updates for distributed scenarios' , ( ) = > {
const config = serviceA . getRealtimeUpdateConfig ( )
expect ( config . enabled ) . toBe ( true )
expect ( config . updateIndex ) . toBe ( true )
expect ( config . updateStatistics ) . toBe ( true )
} )
it ( 'should handle skipCache option correctly' , async ( ) = > {
// Add test data
await serviceA . add ( {
id : 'item-1' ,
text : 'skip cache test'
} )
// Search with cache
2025-08-26 12:03:45 -07:00
const results1 = await serviceA . search ( 'skip cache' , { limit : 5 } )
2025-08-25 09:52:32 -07:00
expect ( results1 . length ) . toBe ( 1 )
// Verify cache is populated
let stats = serviceA . getCacheStats ( )
expect ( stats . search . size ) . toBe ( 1 )
// Search with skipCache - should bypass cache
2025-08-26 12:03:45 -07:00
const results2 = await serviceA . search ( 'skip cache' , { limit : 5 , skipCache : true } )
2025-08-25 09:52:32 -07:00
expect ( results2 . length ) . toBe ( 1 )
// Cache size shouldn't increase
stats = serviceA . getCacheStats ( )
expect ( stats . search . size ) . toBe ( 1 ) // Still just one entry
} )
} )
describe ( 'Distributed Mode Best Practices' , ( ) = > {
it ( 'should work with recommended distributed settings' , async ( ) = > {
const distributedService = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
searchCache : {
enabled : true ,
maxAge : 180000 , // 3 minutes - shorter for distributed
maxSize : 100
} ,
realtimeUpdates : {
enabled : true ,
interval : 30000 , // 30 seconds
updateIndex : true ,
updateStatistics : true
} ,
logging : { verbose : false }
} )
await distributedService . init ( )
// Verify configuration
const config = distributedService . getRealtimeUpdateConfig ( )
expect ( config . enabled ) . toBe ( true )
expect ( config . interval ) . toBe ( 30000 )
const cacheStats = distributedService . getCacheStats ( )
expect ( cacheStats . search . enabled ) . toBe ( true )
2025-08-25 17:12:58 -07:00
await distributedService . clearAll ( { force : true } )
2025-08-25 09:52:32 -07:00
} )
it ( 'should maintain performance with frequent external changes' , async ( ) = > {
// Simulate a scenario with frequent external changes
const queries = [ 'query1' , 'query2' , 'query3' , 'query4' , 'query5' ]
// Add initial data
for ( let i = 0 ; i < 20 ; i ++ ) {
await serviceA . add ( {
id : ` item- ${ i } ` ,
text : ` data for query ${ ( i % 5 ) + 1 } item ${ i } `
} )
}
// Clear cache to start measurement
serviceA . clearCache ( )
// Perform multiple searches (some will be cache hits)
for ( const query of queries ) {
2025-08-26 12:03:45 -07:00
await serviceA . search ( query , { limit : 5 } ) // First search - cache miss
await serviceA . search ( query , { limit : 5 } ) // Second search - cache hit
2025-08-25 09:52:32 -07:00
}
const stats = serviceA . getCacheStats ( )
// Should have good hit rate despite distributed scenario
expect ( stats . search . hitRate ) . toBeGreaterThan ( 0.4 ) // At least 40%
expect ( stats . search . hits ) . toBeGreaterThan ( 0 )
expect ( stats . search . size ) . toBe ( queries . length )
} )
} )
} )