test(8.0): re-home orphaned test files into the gate + guard against recurrence

27 test files matched no vitest config, so they never ran in CI and gave false
coverage confidence (the drift the audit flagged).

- Re-homed 10 functional suites into the gate (renamed to *.unit.test.ts /
  *.integration.test.ts): Transaction + TransactionManager, type-utils,
  integrations/core + odata, comprehensive/public-api-complete, regression
  (metadata-index-cleanup + v5.7.0-deadlock), vfs/tree-operations +
  vfs-bulkwrite-race. ~192 previously-dark tests now run and pass.
- Deleted 8 bit-rotted/redundant files that fail against 8.0 and never ran:
  one had a literal syntax error; one used filesystem writer-locks that polluted
  parallel runs; the rest assert old "Brainy 3.0/v3.0" APIs already covered by
  the live suites (api/batch-operations + crud-operations-enhanced, brainy-3,
  comprehensive/core-api + find-triple-intelligence, streaming-pipeline,
  vfs/vfs-relationships, transaction/integration/typeaware-transactions).
- Added a coverage guard (tests/unit/test-suite-coverage-guard.test.ts) that
  FAILS CI if any *.test.ts ever again falls outside every config, with an
  explicit, conscious MANUAL_ONLY allowlist for the 9 genuine benchmark / perf /
  model-load files that are run by hand.

Gate green: unit 1712, integration 607.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Snelling 2026-06-29 11:47:18 -07:00
parent 5f974abc8a
commit 3f9f140c8c
19 changed files with 69 additions and 3658 deletions

View file

@ -1,660 +0,0 @@
/**
* Batch Operations Test Suite for Brainy v3.0
*
* Comprehensive testing of batch operations including:
* - addMany: Bulk entity creation
* - removeMany: Bulk deletion
* - Performance validation at scale
* - Memory efficiency testing
* - Error recovery scenarios
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy'
import { performance } from 'perf_hooks'
interface BatchMetrics {
operation: string
totalItems: number
successCount: number
failureCount: number
duration: number
throughput: number
memoryUsed: number
errors: string[]
}
class BatchMetricsCollector {
private metrics: BatchMetrics[] = []
recordBatch(
operation: string,
totalItems: number,
results: any[],
duration: number,
memoryUsed: number
): BatchMetrics {
const successCount = results.filter(r =>
r?.status === 'fulfilled' || r === true || (r && !r.error)
).length
const failureCount = totalItems - successCount
const errors = results
.filter(r => r?.status === 'rejected' || r?.error)
.map(r => r?.reason?.message || r?.error || 'Unknown error')
.slice(0, 5) // Limit to first 5 errors
const metric: BatchMetrics = {
operation,
totalItems,
successCount,
failureCount,
duration,
throughput: (totalItems / duration) * 1000,
memoryUsed,
errors
}
this.metrics.push(metric)
return metric
}
getMetrics(): BatchMetrics[] {
return this.metrics
}
generateReport(): void {
console.log('\n=== Batch Operations Performance Report ===\n')
console.table(this.metrics.map(m => ({
Operation: m.operation,
'Total Items': m.totalItems,
'Success': `${m.successCount}/${m.totalItems} (${((m.successCount/m.totalItems)*100).toFixed(1)}%)`,
'Duration (ms)': m.duration.toFixed(2),
'Throughput (items/s)': m.throughput.toFixed(0),
'Memory (MB)': (m.memoryUsed / 1024 / 1024).toFixed(2),
'Errors': m.errors.length > 0 ? m.errors[0] : 'None'
})))
// Summary statistics
const totalOps = this.metrics.reduce((sum, m) => sum + m.totalItems, 0)
const totalSuccess = this.metrics.reduce((sum, m) => sum + m.successCount, 0)
const avgThroughput = this.metrics.reduce((sum, m) => sum + m.throughput, 0) / this.metrics.length
console.log('\nSummary:')
console.log(`Total Operations: ${totalOps}`)
console.log(`Success Rate: ${((totalSuccess/totalOps)*100).toFixed(2)}%`)
console.log(`Average Throughput: ${avgThroughput.toFixed(0)} items/sec`)
}
}
describe('Batch Operations - Public API Testing', () => {
let brainy: Brainy
let metricsCollector: BatchMetricsCollector
beforeEach(async () => {
brainy = new Brainy({ requireSubtype: false,
storage: { type: 'memory' }
})
await brainy.init()
metricsCollector = new BatchMetricsCollector()
})
afterEach(async () => {
await brainy.close()
if (global.gc) global.gc()
})
describe('addMany - Bulk Entity Creation', () => {
it('should add 100 entities efficiently', async () => {
const entities = Array(100).fill(null).map((_, i) => ({
data: `Batch entity ${i}: Test content for bulk operations`,
type: 'document' as const,
metadata: {
batchIndex: i,
batchId: 'batch-100',
timestamp: Date.now()
}
}))
const startMemory = process.memoryUsage().heapUsed
const startTime = performance.now()
const result = await brainy.addMany({ items: entities })
const duration = performance.now() - startTime
const memoryUsed = process.memoryUsage().heapUsed - startMemory
const metric = metricsCollector.recordBatch(
'addMany-100',
entities.length,
result.successful,
duration,
memoryUsed
)
expect(result.successful).toHaveLength(100)
expect(result.failed).toHaveLength(0)
expect(metric.throughput).toBeGreaterThan(100) // > 100 items/sec
})
it('should handle 1,000 entities with proper batching', async () => {
const entities = Array(1000).fill(null).map((_, i) => ({
data: `Entity ${i}: ${' '.repeat(100)}`, // ~100 bytes each
type: 'document' as const,
metadata: {
index: i,
category: `cat-${i % 10}`,
tags: [`tag-${i % 5}`, `tag-${i % 7}`]
}
}))
const startMemory = process.memoryUsage().heapUsed
const startTime = performance.now()
const result = await brainy.addMany({
items: entities,
chunkSize: 100, // Process in batches
parallel: true
})
const duration = performance.now() - startTime
const memoryUsed = process.memoryUsage().heapUsed - startMemory
metricsCollector.recordBatch(
'addMany-1k',
entities.length,
result.successful,
duration,
memoryUsed
)
expect(result.successful.length).toBe(1000)
expect(result.failed.length).toBe(0)
expect(duration).toBeLessThan(10000) // < 10 seconds
// Verify data integrity
const sampleIds = result.successful.slice(0, 10)
for (const id of sampleIds) {
const entity = await brainy.get(id)
expect(entity).toBeDefined()
}
})
it('should handle 10,000 entities with memory efficiency', async () => {
const chunkSize = 1000
const totalEntities = 10000
let allSuccessful: string[] = []
let allFailed: any[] = []
const startMemory = process.memoryUsage().heapUsed
const startTime = performance.now()
// Process in chunks to avoid memory issues
for (let chunk = 0; chunk < totalEntities; chunk += chunkSize) {
const entities = Array(Math.min(chunkSize, totalEntities - chunk))
.fill(null)
.map((_, i) => ({
data: `Chunk entity ${chunk + i}`,
type: 'document' as const,
metadata: { globalIndex: chunk + i }
}))
const result = await brainy.addMany({ items: entities })
allSuccessful.push(...result.successful)
allFailed.push(...result.failed)
// Check memory usage
const currentMemory = process.memoryUsage().heapUsed
const memoryGrowth = currentMemory - startMemory
expect(memoryGrowth).toBeLessThan(500 * 1024 * 1024) // < 500MB total
}
const duration = performance.now() - startTime
const finalMemory = process.memoryUsage().heapUsed - startMemory
metricsCollector.recordBatch(
'addMany-10k',
totalEntities,
allSuccessful,
duration,
finalMemory
)
expect(allSuccessful.length).toBe(10000)
expect(allFailed.length).toBe(0)
})
it('should handle partial failures gracefully', async () => {
const entities = [
{ data: 'Valid entity 1', type: 'document' as const },
{ data: '', type: 'document' as const }, // Invalid: empty data
{ data: 'Valid entity 2', type: 'document' as const },
{ data: null as any, type: 'document' as const }, // Invalid: null data
{ data: 'Valid entity 3', type: 'document' as const },
]
const result = await brainy.addMany({
items: entities,
continueOnError: true
})
// Should process valid entities even if some fail
expect(result.successful.length).toBeGreaterThanOrEqual(3)
expect(result.failed.length).toBeGreaterThanOrEqual(0) // May or may not validate
// Verify valid entities were added
for (const id of result.successful) {
const entity = await brainy.get(id)
expect(entity).toBeDefined()
}
})
it('should support concurrent batch operations', async () => {
const batches = Array(5).fill(null).map((_, batchIdx) =>
Array(100).fill(null).map((_, i) => ({
data: `Batch ${batchIdx} Entity ${i}`,
type: 'document' as const,
metadata: { batchId: batchIdx, index: i }
}))
)
const startTime = performance.now()
// Execute batches concurrently
const results = await Promise.all(
batches.map(entities => brainy.addMany({ items: entities }))
)
const duration = performance.now() - startTime
expect(results).toHaveLength(5)
const totalSuccess = results.reduce((sum, r) => sum + r.successful.length, 0)
expect(totalSuccess).toBe(500)
expect(duration).toBeLessThan(5000) // Concurrent should be faster
})
})
let testIds: string[] = []
beforeEach(async () => {
// Create test entities
const entities = Array(100).fill(null).map((_, i) => ({
data: `Original content ${i}`,
type: 'document' as const,
metadata: { version: 1, index: i }
}))
const result = await brainy.addMany({ items: entities })
testIds = result.successful
})
it('should update multiple entities by IDs', async () => {
const updates = testIds.slice(0, 50).map(id => ({
id,
updates: {
metadata: {
version: 2,
updatedAt: Date.now()
}
}
}))
const startTime = performance.now()
const results = await Promise.all(
updates.map(u => brainy.update({ id: u.id, ...u.updates }))
)
const duration = performance.now() - startTime
metricsCollector.recordBatch(
updates.length,
results,
duration,
0
)
// Verify updates
const sample = await brainy.get(testIds[0])
expect(sample?.metadata?.version).toBe(2)
})
it('should update entities matching criteria', async () => {
// Update all entities with index < 20
const targetIds = testIds.slice(0, 20)
const startTime = performance.now()
const results = await Promise.all(
targetIds.map(id =>
brainy.update({
id,
metadata: {
status: 'updated',
processedAt: Date.now()
}
})
)
)
const duration = performance.now() - startTime
metricsCollector.recordBatch(
targetIds.length,
results,
duration,
0
)
// Verify updates
for (const id of targetIds.slice(0, 5)) {
const entity = await brainy.get(id)
expect(entity?.metadata?.status).toBe('updated')
}
})
})
describe('removeMany - Bulk Deletion', () => {
let testIds: string[] = []
beforeEach(async () => {
// Create test entities
const entities = Array(200).fill(null).map((_, i) => ({
data: `Delete test ${i}`,
type: 'document' as const,
metadata: { deleteGroup: i % 4 }
}))
const result = await brainy.addMany({ items: entities })
testIds = result.successful
})
it('should delete multiple entities by IDs', async () => {
const toDelete = testIds.slice(0, 100)
const startMemory = process.memoryUsage().heapUsed
const startTime = performance.now()
// Use removeMany if available, otherwise individual deletes
let results: any[]
if ('removeMany' in brainy && typeof brainy.removeMany === 'function') {
const result = await brainy.removeMany({ ids: toDelete })
results = result.successful
} else {
results = await Promise.all(
toDelete.map(id => brainy.remove(id))
)
}
const duration = performance.now() - startTime
const memoryUsed = process.memoryUsage().heapUsed - startMemory
metricsCollector.recordBatch(
'removeMany-100',
toDelete.length,
results,
duration,
memoryUsed
)
// Verify deletion
for (const id of toDelete.slice(0, 10)) {
const entity = await brainy.get(id)
expect(entity).toBeNull()
}
// Remaining entities should still exist
const remaining = testIds.slice(100, 110)
for (const id of remaining) {
const entity = await brainy.get(id)
expect(entity).toBeDefined()
}
})
it('should handle large-scale deletion efficiently', async () => {
// Create a large dataset
const largeDataset = Array(1000).fill(null).map((_, i) => ({
data: `Large scale delete test ${i}`,
type: 'document' as const
}))
const addResult = await brainy.addMany({ items: largeDataset })
const idsToDelete = addResult.successful
const startTime = performance.now()
// Delete in batches
const batchSize = 100
for (let i = 0; i < idsToDelete.length; i += batchSize) {
const batch = idsToDelete.slice(i, i + batchSize)
await Promise.all(batch.map(id => brainy.remove(id)))
}
const duration = performance.now() - startTime
metricsCollector.recordBatch(
'removeMany-1k',
idsToDelete.length,
idsToDelete.map(() => true),
duration,
0
)
expect(duration).toBeLessThan(10000) // < 10 seconds
})
})
let nodeIds: string[] = []
beforeEach(async () => {
// Create nodes for relationships
const nodes = Array(50).fill(null).map((_, i) => ({
data: `Node ${i}`,
type: 'thing' as const,
metadata: { nodeIndex: i }
}))
const result = await brainy.addMany({ items: nodes })
nodeIds = result.successful
})
it('should create multiple relationships efficiently', async () => {
const relationships: Array<{from: string, to: string, type: string}> = []
// Create a connected graph
for (let i = 0; i < nodeIds.length - 1; i++) {
relationships.push({
from: nodeIds[i],
to: nodeIds[i + 1],
type: 'connected_to'
})
}
const startTime = performance.now()
const results = await Promise.all(
relationships.map(r =>
brainy.relate({ from: r.from, to: r.to, type: r.type as any })
)
)
const duration = performance.now() - startTime
metricsCollector.recordBatch(
relationships.length,
results,
duration,
0
)
expect(results.every(r => typeof r === 'string')).toBe(true) // relate returns ID
expect(duration).toBeLessThan(5000)
})
it('should create complex graph structures', async () => {
// Create a fully connected subgraph (first 10 nodes)
const subgraph = nodeIds.slice(0, 10)
const relationships: Array<{from: string, to: string}> = []
for (let i = 0; i < subgraph.length; i++) {
for (let j = i + 1; j < subgraph.length; j++) {
relationships.push({
from: subgraph[i],
to: subgraph[j]
})
}
}
const startTime = performance.now()
const results = await Promise.all(
relationships.map(r =>
brainy.relate({ from: r.from, to: r.to, type: 'relatedTo' })
)
)
const duration = performance.now() - startTime
metricsCollector.recordBatch(
relationships.length,
results,
duration,
0
)
expect(relationships.length).toBe(45) // 10 choose 2
expect(results.every(r => typeof r === 'string')).toBe(true) // relate returns ID
})
})
describe('Mixed Batch Operations', () => {
it('should handle mixed operation types concurrently', async () => {
// Prepare different operation types
const addOps = Array(100).fill(null).map((_, i) => ({
data: `Mixed add ${i}`,
type: 'document' as const
}))
// Add initial entities for update/delete
const setupResult = await brainy.addMany({
items: Array(100).fill(null).map((_, i) => ({
data: `Setup ${i}`,
type: 'document' as const
}))
})
const updateIds = setupResult.successful.slice(0, 50)
const deleteIds = setupResult.successful.slice(50, 100)
const startTime = performance.now()
// Execute all operations concurrently
const [addResults, updateResults, deleteResults] = await Promise.all([
brainy.addMany({ items: addOps }),
Promise.all(updateIds.map(id =>
brainy.update({ id, metadata: { updated: true } })
)),
Promise.all(deleteIds.map(id => brainy.remove(id)))
])
const duration = performance.now() - startTime
console.log(`Mixed operations completed in ${duration.toFixed(2)}ms`)
expect(addResults.successful.length).toBe(100)
expect(updateResults.length).toBe(50)
// Delete returns void, so just check length
expect(deleteResults).toHaveLength(50)
})
})
describe('Error Recovery', () => {
it('should recover from transient failures', async () => {
let attemptCount = 0
const entities = Array(10).fill(null).map((_, i) => ({
data: `Retry test ${i}`,
type: 'document' as const
}))
// Simulate transient failures
const originalAdd = brainy.add.bind(brainy)
brainy.add = async function(params: any) {
attemptCount++
if (attemptCount % 3 === 0) {
throw new Error('Transient failure')
}
return originalAdd(params)
}
const results: any[] = []
for (const entity of entities) {
let retries = 0
while (retries < 3) {
try {
const id = await brainy.add(entity)
results.push({ status: 'fulfilled', value: id })
break
} catch (error) {
retries++
if (retries === 3) {
results.push({ status: 'rejected', reason: error })
}
}
}
}
const successful = results.filter(r => r.status === 'fulfilled')
expect(successful.length).toBeGreaterThanOrEqual(6) // Most should succeed with retry
})
it('should handle memory pressure gracefully', async () => {
const largeEntities = Array(100).fill(null).map((_, i) => ({
data: 'x'.repeat(100000), // 100KB each = 10MB total
type: 'document' as const,
metadata: { index: i }
}))
const startMemory = process.memoryUsage().heapUsed
// Process with memory monitoring
const batchSize = 10
const results: any[] = []
for (let i = 0; i < largeEntities.length; i += batchSize) {
const batch = largeEntities.slice(i, i + batchSize)
// Check memory before processing
const currentMemory = process.memoryUsage().heapUsed
const memoryUsed = currentMemory - startMemory
if (memoryUsed > 100 * 1024 * 1024) { // If > 100MB
if (global.gc) global.gc() // Force GC if available
}
const batchResult = await brainy.addMany({ items: batch })
results.push(...batchResult.successful)
}
expect(results.length).toBe(100)
const finalMemory = process.memoryUsage().heapUsed - startMemory
expect(finalMemory).toBeLessThan(200 * 1024 * 1024) // Should stay under 200MB
})
})
describe('Performance Report', () => {
it('should generate comprehensive batch operations report', async () => {
metricsCollector.generateReport()
const metrics = metricsCollector.getMetrics()
expect(metrics.length).toBeGreaterThan(0)
// Validate performance targets
for (const metric of metrics) {
expect(metric.successCount).toBeGreaterThan(0)
if (metric.operation.includes('100')) {
expect(metric.throughput).toBeGreaterThan(50) // At least 50 items/sec
}
}
})
})
})

View file

@ -1,764 +0,0 @@
/**
* Enhanced CRUD Operations Test Suite for Brainy v3.0
*
* Comprehensive validation of all public API CRUD operations with:
* - Exhaustive edge case testing
* - Performance benchmarking
* - Memory leak detection
* - Concurrent operation validation
* - Large-scale data handling
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy'
import { performance } from 'perf_hooks'
interface PerformanceMetrics {
operation: string
itemCount: number
duration: number
memoryUsed: number
itemsPerSecond: number
percentiles?: {
p50: number
p95: number
p99: number
}
}
class TestMetricsCollector {
private metrics: PerformanceMetrics[] = []
private initialMemory: number = 0
private latencies: Map<string, number[]> = new Map()
startOperation(operation: string, itemCount: number = 1): number {
this.initialMemory = process.memoryUsage().heapUsed
return performance.now()
}
endOperation(operation: string, itemCount: number, startTime: number): PerformanceMetrics {
const duration = performance.now() - startTime
const memoryUsed = process.memoryUsage().heapUsed - this.initialMemory
const itemsPerSecond = (itemCount / duration) * 1000
// Track latencies for percentile calculations
if (!this.latencies.has(operation)) {
this.latencies.set(operation, [])
}
this.latencies.get(operation)!.push(duration / itemCount)
const metric: PerformanceMetrics = {
operation,
itemCount,
duration,
memoryUsed,
itemsPerSecond
}
this.metrics.push(metric)
return metric
}
calculatePercentiles(operation: string): { p50: number; p95: number; p99: number } | undefined {
const latencies = this.latencies.get(operation)
if (!latencies || latencies.length === 0) return undefined
const sorted = [...latencies].sort((a, b) => a - b)
return {
p50: sorted[Math.floor(sorted.length * 0.5)],
p95: sorted[Math.floor(sorted.length * 0.95)],
p99: sorted[Math.floor(sorted.length * 0.99)]
}
}
getMetrics(): PerformanceMetrics[] {
return this.metrics
}
async reportMetrics(): Promise<void> {
console.log('\n=== Performance Metrics Report ===\n')
// Add percentiles to metrics
for (const metric of this.metrics) {
metric.percentiles = this.calculatePercentiles(metric.operation)
}
console.table(this.metrics.map(m => ({
Operation: m.operation,
'Items': m.itemCount,
'Duration (ms)': m.duration.toFixed(2),
'Items/sec': m.itemsPerSecond.toFixed(0),
'Memory (MB)': (m.memoryUsed / 1024 / 1024).toFixed(2),
'P50 (ms)': m.percentiles?.p50.toFixed(2) || 'N/A',
'P95 (ms)': m.percentiles?.p95.toFixed(2) || 'N/A',
'P99 (ms)': m.percentiles?.p99.toFixed(2) || 'N/A'
})))
}
}
describe('Enhanced CRUD Operations - Public API Validation', () => {
let brainy: Brainy
let metricsCollector: TestMetricsCollector
beforeEach(async () => {
brainy = new Brainy({ requireSubtype: false,
storage: { type: 'memory' }
})
await brainy.init()
metricsCollector = new TestMetricsCollector()
})
afterEach(async () => {
await brainy.close()
// Force garbage collection if available
if (global.gc) {
global.gc()
}
})
describe('ADD Operations - Comprehensive Testing', () => {
describe('Basic Add Functionality', () => {
it('should add entity with all supported data types', async () => {
const testData = {
text: 'This is a comprehensive test document about AI and machine learning',
metadata: {
title: 'Test Document',
author: 'Test Suite',
version: '1.0.0',
tags: ['test', 'validation', 'comprehensive'],
stats: {
words: 10,
characters: 68,
readingTime: 2
},
nullField: null,
booleanField: true,
numberField: 42.5,
dateField: new Date().toISOString()
}
}
const startTime = metricsCollector.startOperation('add-single')
const id = await brainy.add({
data: testData.text,
type: 'document' as const,
metadata: testData.metadata
})
const metric = metricsCollector.endOperation('add-single', 1, startTime)
expect(id).toBeDefined()
expect(typeof id).toBe('string')
expect(id.length).toBeGreaterThan(0)
expect(metric.duration).toBeLessThan(50) // 50ms SLA for single add
// Verify stored data
const retrieved = await brainy.get(id)
expect(retrieved).toBeDefined()
expect(retrieved?.metadata?.title).toBe('Test Document')
expect(retrieved?.type).toBe('document')
expect(retrieved?.metadata?.title).toBe('Test Document')
})
it('should handle Unicode and special characters correctly', async () => {
const specialCases = [
{ data: '😀🎉🚀 Emoji test document', desc: 'Emoji support' },
{ data: '中文测试文档 Chinese text', desc: 'Chinese characters' },
{ data: 'العربية اختبار Arabic text', desc: 'Arabic script' },
{ data: 'Русский текст тестирование', desc: 'Cyrillic script' },
{ data: '日本語のテストドキュメント', desc: 'Japanese characters' },
{ data: 'Line\nBreak\rReturn\tTab test', desc: 'Control characters' },
{ data: '<script>alert("XSS")</script>', desc: 'HTML injection attempt' },
{ data: '../../etc/passwd traversal', desc: 'Path traversal attempt' },
{ data: 'NULL\x00byte test', desc: 'Null byte injection' },
{ data: '𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁', desc: 'Mathematical alphanumeric symbols' }
]
for (const testCase of specialCases) {
const id = await brainy.add({
data: testCase.data,
type: 'document' as const,
metadata: { description: testCase.desc }
})
const retrieved = await brainy.get(id)
expect(retrieved?.data).toBe(testCase.data)
expect(retrieved?.metadata?.description).toBe(testCase.desc)
}
})
it('should auto-generate unique IDs when not provided', async () => {
const ids = new Set<string>()
const count = 100
for (let i = 0; i < count; i++) {
const id = await brainy.add({
data: `Auto-generated ID test ${i}`,
type: 'test'
})
ids.add(id)
}
expect(ids.size).toBe(count) // All IDs must be unique
// Verify ID format
for (const id of ids) {
expect(id).toMatch(/^[a-zA-Z0-9_-]+$/) // Valid ID characters
expect(id.length).toBeGreaterThanOrEqual(8) // Reasonable length
expect(id.length).toBeLessThanOrEqual(64) // Not too long
}
})
})
describe('Bulk Add Operations', () => {
it('should efficiently handle 1,000 entities', async () => {
const entities = Array(1000).fill(null).map((_, i) => ({
data: `Test document ${i}: This is content for testing bulk operations at scale`,
type: 'document',
metadata: {
index: i,
category: `category-${i % 10}`,
score: Math.random() * 100,
tags: [`tag-${i % 5}`, `tag-${i % 7}`]
}
}))
const startTime = metricsCollector.startOperation('add-bulk-1k', 1000)
const ids: string[] = []
// Add in batches for better performance
const batchSize = 100
for (let i = 0; i < entities.length; i += batchSize) {
const batch = entities.slice(i, i + batchSize)
const batchIds = await Promise.all(
batch.map(e => brainy.add(e))
)
ids.push(...batchIds)
}
const metric = metricsCollector.endOperation('add-bulk-1k', 1000, startTime)
expect(ids).toHaveLength(1000)
expect(new Set(ids).size).toBe(1000) // All unique
expect(metric.itemsPerSecond).toBeGreaterThan(100) // At least 100 items/sec
expect(metric.memoryUsed).toBeLessThan(100 * 1024 * 1024) // Less than 100MB
// Verify data integrity with random sampling
for (let i = 0; i < 10; i++) {
const randomIdx = Math.floor(Math.random() * 1000)
const entity = await brainy.get(ids[randomIdx])
expect(entity?.metadata?.index).toBe(randomIdx)
}
})
it('should handle 10,000 entities with memory efficiency', async () => {
const startMemory = process.memoryUsage().heapUsed
const count = 10000
const dataSize = 500 // bytes per entity
const startTime = metricsCollector.startOperation('add-bulk-10k', count)
const ids: string[] = []
// Stream-like processing
const batchSize = 500
for (let i = 0; i < count; i += batchSize) {
const batch = Array(Math.min(batchSize, count - i)).fill(null).map((_, j) => ({
data: 'x'.repeat(dataSize) + ` Entity ${i + j}`,
type: 'bulk-test',
metadata: { index: i + j }
}))
const batchIds = await Promise.all(
batch.map(e => brainy.add(e))
)
ids.push(...batchIds)
// Check memory periodically
if (i % 2000 === 0 && i > 0) {
const currentMemory = process.memoryUsage().heapUsed
const memoryGrowth = currentMemory - startMemory
expect(memoryGrowth).toBeLessThan(200 * 1024 * 1024) // Less than 200MB
}
}
const metric = metricsCollector.endOperation('add-bulk-10k', count, startTime)
expect(ids).toHaveLength(count)
expect(metric.duration).toBeLessThan(60000) // Complete within 60 seconds
expect(metric.itemsPerSecond).toBeGreaterThan(150) // At least 150 items/sec
})
it('should detect and prevent memory leaks', async () => {
const iterations = 5
const memoryDeltas: number[] = []
for (let iter = 0; iter < iterations; iter++) {
if (global.gc) global.gc() // Force GC if available
const beforeMemory = process.memoryUsage().heapUsed
// Add and delete 500 entities
const ids: string[] = []
for (let i = 0; i < 500; i++) {
const id = await brainy.add({
data: 'x'.repeat(1000), // 1KB per entity
type: 'leak-test',
metadata: { iteration: iter, index: i }
})
ids.push(id)
}
// Delete all entities
for (const id of ids) {
await brainy.remove(id)
}
if (global.gc) global.gc()
const afterMemory = process.memoryUsage().heapUsed
memoryDeltas.push(afterMemory - beforeMemory)
}
// Memory should stabilize, not continuously grow
const avgFirstTwo = (memoryDeltas[0] + memoryDeltas[1]) / 2
const avgLastTwo = (memoryDeltas[3] + memoryDeltas[4]) / 2
const growth = avgLastTwo - avgFirstTwo
expect(growth).toBeLessThan(10 * 1024 * 1024) // Less than 10MB growth
})
})
describe('Concurrent Operations', () => {
it('should handle 100 concurrent adds without race conditions', async () => {
const concurrentCount = 100
const operations = Array(concurrentCount).fill(null).map((_, i) => ({
data: `Concurrent test document ${i}`,
type: 'concurrent-test',
metadata: {
index: i,
timestamp: Date.now(),
random: Math.random()
}
}))
const startTime = performance.now()
const ids = await Promise.all(
operations.map(op => brainy.add(op))
)
const duration = performance.now() - startTime
expect(ids).toHaveLength(concurrentCount)
expect(new Set(ids).size).toBe(concurrentCount) // All unique
expect(duration).toBeLessThan(2000) // Complete within 2 seconds
// Verify data integrity
for (let i = 0; i < 10; i++) {
const randomIdx = Math.floor(Math.random() * concurrentCount)
const entity = await brainy.get(ids[randomIdx])
expect(entity?.metadata?.index).toBe(randomIdx)
}
})
it('should handle write conflicts gracefully', async () => {
// Test optimistic concurrency control
const id = await brainy.add({
data: 'Original content',
type: 'conflict-test',
metadata: { version: 1 }
})
// Simulate concurrent updates
const updates = Array(10).fill(null).map((_, i) =>
brainy.update(id, {
metadata: { version: i + 2, updatedBy: `process-${i}` }
})
)
const results = await Promise.allSettled(updates)
// At least one should succeed
const succeeded = results.filter(r => r.status === 'fulfilled')
expect(succeeded.length).toBeGreaterThanOrEqual(1)
// Final state should be consistent
const final = await brainy.get(id)
expect(final?.metadata?.version).toBeGreaterThanOrEqual(2)
})
})
describe('Input Validation', () => {
it('should reject invalid input types', async () => {
const invalidCases = [
{ data: null, desc: 'null data' },
{ data: undefined, desc: 'undefined data' },
{ data: '', desc: 'empty string' },
{ data: 42, desc: 'number instead of string' },
{ data: true, desc: 'boolean instead of string' },
{ data: [], desc: 'array instead of string' },
{ data: {}, desc: 'object instead of string' }
]
for (const testCase of invalidCases) {
await expect(
brainy.add({
data: testCase.data as any,
type: 'test'
})
).rejects.toThrow()
}
})
it('should handle extremely large entities', async () => {
const largeData = 'x'.repeat(5 * 1024 * 1024) // 5MB
const startTime = performance.now()
const id = await brainy.add({
data: largeData,
type: 'large-entity',
metadata: { size: largeData.length }
})
const duration = performance.now() - startTime
expect(id).toBeDefined()
expect(duration).toBeLessThan(5000) // Handle within 5 seconds
const retrieved = await brainy.get(id)
expect(retrieved?.data.length).toBe(largeData.length)
})
it('should sanitize and validate metadata fields', async () => {
const metadata = {
'normal-field': 'value1',
'$special.field': 'value2',
'__proto__': 'attempt-pollution',
'constructor': 'attempt-override',
'../../../etc/passwd': 'path-traversal',
'<script>alert("xss")</script>': 'xss-attempt'
}
const id = await brainy.add({
data: 'Test document with suspicious metadata',
type: 'security-test',
metadata
})
const retrieved = await brainy.get(id)
expect(retrieved).toBeDefined()
// Verify prototype pollution prevention
expect(Object.prototype).not.toHaveProperty('attempt-pollution')
expect(Object.constructor).not.toBe('attempt-override')
})
it('should handle circular references gracefully', async () => {
const metadata: any = {
name: 'Circular test',
level: 1
}
metadata.self = metadata // Create circular reference
// Should either serialize correctly or throw meaningful error
try {
const id = await brainy.add({
data: 'Document with circular metadata',
type: 'circular-test',
metadata
})
const retrieved = await brainy.get(id)
expect(retrieved).toBeDefined()
// Circular reference should be handled (removed or converted)
expect(retrieved?.metadata?.self).not.toBe(retrieved?.metadata)
} catch (error: any) {
expect(error.message).toMatch(/circular|cyclic/i)
}
})
})
describe('Performance SLAs', () => {
it('should meet latency SLAs for single operations', async () => {
const operations = 100
const latencies: number[] = []
for (let i = 0; i < operations; i++) {
const start = performance.now()
await brainy.add({
data: `Performance test document ${i}`,
type: 'perf-test',
metadata: { index: i }
})
latencies.push(performance.now() - start)
}
// Calculate percentiles
latencies.sort((a, b) => a - b)
const p50 = latencies[Math.floor(latencies.length * 0.5)]
const p95 = latencies[Math.floor(latencies.length * 0.95)]
const p99 = latencies[Math.floor(latencies.length * 0.99)]
console.log(`Add Latencies - P50: ${p50.toFixed(2)}ms, P95: ${p95.toFixed(2)}ms, P99: ${p99.toFixed(2)}ms`)
expect(p50).toBeLessThan(10) // P50 < 10ms
expect(p95).toBeLessThan(50) // P95 < 50ms
expect(p99).toBeLessThan(100) // P99 < 100ms
})
it('should maintain throughput under sustained load', async () => {
const duration = 5000 // 5 seconds
const startTime = performance.now()
let operationCount = 0
while (performance.now() - startTime < duration) {
await brainy.add({
data: `Sustained load test ${operationCount}`,
type: 'load-test',
metadata: { timestamp: Date.now() }
})
operationCount++
}
const actualDuration = performance.now() - startTime
const throughput = (operationCount / actualDuration) * 1000
console.log(`Sustained Load - Ops: ${operationCount}, Throughput: ${throughput.toFixed(2)} ops/sec`)
expect(throughput).toBeGreaterThan(50) // At least 50 ops/sec sustained
})
})
})
describe('GET Operations - Comprehensive Testing', () => {
let testIds: string[] = []
beforeEach(async () => {
// Seed test data
testIds = []
for (let i = 0; i < 10; i++) {
const id = await brainy.add({
data: `Test document ${i}`,
type: 'test',
metadata: { index: i, category: `cat-${i % 3}` }
})
testIds.push(id)
}
})
it('should retrieve existing entities', async () => {
const startTime = metricsCollector.startOperation('get-single')
const entity = await brainy.get(testIds[0])
metricsCollector.endOperation('get-single', 1, startTime)
expect(entity).toBeDefined()
expect(entity?.data).toBe('Test document 0')
expect(entity?.metadata?.index).toBe(0)
})
it('should return null for non-existent IDs', async () => {
const result = await brainy.get('non-existent-id-12345')
expect(result).toBeNull()
})
it('should handle batch retrieval efficiently', async () => {
const startTime = metricsCollector.startOperation('get-batch', testIds.length)
const entities = await Promise.all(
testIds.map(id => brainy.get(id))
)
const metric = metricsCollector.endOperation('get-batch', testIds.length, startTime)
expect(entities.every(e => e !== null)).toBe(true)
expect(metric.itemsPerSecond).toBeGreaterThan(100)
})
})
describe('UPDATE Operations - Comprehensive Testing', () => {
let testId: string
beforeEach(async () => {
testId = await brainy.add({
data: 'Original content',
type: 'update-test',
metadata: { version: 1, status: 'draft' }
})
})
it('should update entity metadata', async () => {
const startTime = metricsCollector.startOperation('update-single')
await brainy.update(testId, {
metadata: { version: 2, status: 'published' }
})
metricsCollector.endOperation('update-single', 1, startTime)
const updated = await brainy.get(testId)
expect(updated?.metadata?.version).toBe(2)
expect(updated?.metadata?.status).toBe('published')
})
it('should handle concurrent updates', async () => {
const updates = Array(10).fill(null).map((_, i) =>
brainy.update(testId, {
metadata: { lastUpdate: i }
})
)
const results = await Promise.allSettled(updates)
const succeeded = results.filter(r => r.status === 'fulfilled')
expect(succeeded.length).toBeGreaterThanOrEqual(1)
})
})
describe('DELETE Operations - Comprehensive Testing', () => {
it('should delete existing entities', async () => {
const id = await brainy.add({
data: 'To be deleted',
type: 'delete-test'
})
const startTime = metricsCollector.startOperation('delete-single')
const result = await brainy.remove(id)
metricsCollector.endOperation('delete-single', 1, startTime)
expect(result).toBe(true)
const retrieved = await brainy.get(id)
expect(retrieved).toBeNull()
})
it('should handle bulk deletions', async () => {
const ids: string[] = []
for (let i = 0; i < 100; i++) {
const id = await brainy.add({
data: `Bulk delete test ${i}`,
type: 'bulk-delete'
})
ids.push(id)
}
const startTime = metricsCollector.startOperation('delete-bulk', ids.length)
const results = await Promise.all(
ids.map(id => brainy.remove(id))
)
metricsCollector.endOperation('delete-bulk', ids.length, startTime)
expect(results.every(r => r === true)).toBe(true)
})
})
describe('RELATE Operations - Comprehensive Testing', () => {
it('should create relationships between entities', async () => {
const id1 = await brainy.add({
data: 'Entity 1',
type: 'node'
})
const id2 = await brainy.add({
data: 'Entity 2',
type: 'node'
})
const startTime = metricsCollector.startOperation('relate-single')
const result = await brainy.relate(id1, id2, 'connects_to')
metricsCollector.endOperation('relate-single', 1, startTime)
expect(result).toBe(true)
})
it('should handle complex graph structures', async () => {
const nodeIds: string[] = []
// Create nodes
for (let i = 0; i < 20; i++) {
const id = await brainy.add({
data: `Node ${i}`,
type: 'graph-node',
metadata: { index: i }
})
nodeIds.push(id)
}
// Create relationships (each node connects to next 3)
const startTime = metricsCollector.startOperation('relate-graph', 60)
for (let i = 0; i < nodeIds.length; i++) {
for (let j = 1; j <= 3; j++) {
const targetIdx = (i + j) % nodeIds.length
await brainy.relate(nodeIds[i], nodeIds[targetIdx], 'links_to')
}
}
metricsCollector.endOperation('relate-graph', 60, startTime)
// Graph should be created successfully
expect(nodeIds).toHaveLength(20)
})
})
describe('FIND Operations - Comprehensive Testing', () => {
beforeEach(async () => {
// Create diverse test dataset
for (let i = 0; i < 50; i++) {
await brainy.add({
data: `Search test document ${i}: Lorem ipsum dolor sit amet`,
type: 'searchable',
metadata: {
index: i,
category: `cat-${i % 5}`,
score: Math.random() * 100,
active: i % 2 === 0
}
})
}
})
it('should find entities by metadata criteria', async () => {
const startTime = metricsCollector.startOperation('find-simple')
const results = await brainy.find({
where: {
'metadata.category': 'cat-0'
}
})
metricsCollector.endOperation('find-simple', 1, startTime)
expect(results.length).toBe(10) // 50 / 5 = 10
})
it('should support pagination', async () => {
const page1 = await brainy.find({
limit: 10,
offset: 0
})
const page2 = await brainy.find({
limit: 10,
offset: 10
})
expect(page1).toHaveLength(10)
expect(page2).toHaveLength(10)
expect(page1[0].id).not.toBe(page2[0].id)
})
it('should handle complex queries', async () => {
const results = await brainy.find({
where: {
'metadata.active': true,
'metadata.score': { $gte: 50 }
},
limit: 100
})
expect(results.every(r => r.metadata?.active === true)).toBe(true)
expect(results.every(r => (r.metadata?.score ?? 0) >= 50)).toBe(true)
})
})
describe('Performance Summary', () => {
it('should generate comprehensive performance report', async () => {
await metricsCollector.reportMetrics()
const metrics = metricsCollector.getMetrics()
expect(metrics.length).toBeGreaterThan(0)
// Validate all operations met SLAs
for (const metric of metrics) {
if (metric.operation.includes('single')) {
expect(metric.duration).toBeLessThan(100) // Single ops < 100ms
}
}
})
})
})

View file

@ -1,431 +0,0 @@
/**
* Brainy 3.0 API Tests
* Comprehensive tests for the new beautiful, consistent API
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy, NounType, VerbType } from '../src/brainy.js'
describe('Brainy 3.0 API', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' }
})
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('Entity Operations', () => {
it('should add entities with beautiful API', async () => {
const id = await brain.add({
data: 'Test document about machine learning',
type: NounType.Document,
metadata: {
title: 'ML Guide',
author: 'Test Author'
},
service: 'test-service'
})
expect(id).toBeTypeOf('string')
expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
})
it('should retrieve entities', async () => {
const id = await brain.add({
data: 'Sample data',
type: NounType.Document,
metadata: { title: 'Sample Title' }
})
// v5.11.1: Need includeVectors to check vectors
const entity = await brain.get(id, { includeVectors: true })
expect(entity).toBeDefined()
expect(entity!.id).toBe(id)
expect(entity!.type).toBe(NounType.Document)
expect(entity!.metadata.title).toBe('Sample Title')
expect(entity!.vector).toBeDefined()
expect(entity!.vector.length).toBeGreaterThan(0)
})
it('should update entities', async () => {
const id = await brain.add({
data: 'Original data',
type: NounType.Document,
metadata: { title: 'Original Title' }
})
await brain.update({
id,
metadata: { title: 'Updated Title', version: 2 }
})
const entity = await brain.get(id)
expect(entity!.metadata.title).toBe('Updated Title')
expect(entity!.metadata.version).toBe(2)
})
it('should delete entities', async () => {
const id = await brain.add({
data: 'To be deleted',
type: NounType.Document
})
await brain.remove(id)
const entity = await brain.get(id)
expect(entity).toBeNull()
})
})
describe('Relationship Operations', () => {
it('should create relationships', async () => {
const doc1 = await brain.add({
data: 'First document',
type: NounType.Document
})
const doc2 = await brain.add({
data: 'Second document',
type: NounType.Document
})
const relationId = await brain.relate({
from: doc1,
to: doc2,
type: VerbType.References,
weight: 0.8,
metadata: { context: 'test' }
})
expect(relationId).toBeTypeOf('string')
})
it('should get relationships', async () => {
const doc1 = await brain.add({
data: 'Document one',
type: NounType.Document
})
const doc2 = await brain.add({
data: 'Document two',
type: NounType.Document
})
await brain.relate({
from: doc1,
to: doc2,
type: VerbType.References
})
const relations = await brain.related({ from: doc1 })
expect(relations).toHaveLength(1)
expect(relations[0].from).toBe(doc1)
expect(relations[0].to).toBe(doc2)
expect(relations[0].type).toBe(VerbType.References)
})
it('should create bidirectional relationships', async () => {
const person = await brain.add({
data: 'John Smith',
type: NounType.Person
})
const org = await brain.add({
data: 'Tech Corp',
type: NounType.Organization
})
await brain.relate({
from: person,
to: org,
type: VerbType.WorksWith,
bidirectional: true
})
const fromPerson = await brain.related({ from: person })
const toPerson = await brain.related({ to: person })
expect(fromPerson).toHaveLength(1)
expect(toPerson).toHaveLength(1)
})
it('should delete relationships', async () => {
const doc1 = await brain.add({
data: 'Doc 1',
type: NounType.Document
})
const doc2 = await brain.add({
data: 'Doc 2',
type: NounType.Document
})
const relationId = await brain.relate({
from: doc1,
to: doc2,
type: VerbType.References
})
await brain.unrelate(relationId)
const relations = await brain.related({ from: doc1 })
expect(relations).toHaveLength(0)
})
})
describe('Search Operations', () => {
beforeEach(async () => {
// Add test data
await brain.add({
data: 'Machine learning is a subset of artificial intelligence',
type: NounType.Document,
metadata: { category: 'AI', importance: 5 }
})
await brain.add({
data: 'Neural networks are used in deep learning',
type: NounType.Document,
metadata: { category: 'AI', importance: 4 }
})
await brain.add({
data: 'JavaScript is a programming language',
type: NounType.Document,
metadata: { category: 'Programming', importance: 3 }
})
})
it('should find entities by text query', async () => {
const results = await brain.find({
query: 'machine learning artificial intelligence',
limit: 2
})
expect(results).toHaveLength(2)
expect(results[0].score).toBeGreaterThan(0)
expect(results[0].entity.type).toBe(NounType.Document)
})
it('should find similar entities', async () => {
const aiDocId = await brain.add({
data: 'Deep learning algorithms',
type: NounType.Document
})
const results = await brain.similar({
to: aiDocId,
limit: 2
})
expect(results.length).toBeGreaterThan(0)
expect(results[0].entity.id).not.toBe(aiDocId) // Shouldn't include self
})
it('should filter by metadata', async () => {
const results = await brain.find({
query: 'learning',
where: { category: 'AI' },
limit: 5
})
results.forEach(result => {
expect(result.entity.metadata.category).toBe('AI')
})
})
it('should filter by entity type', async () => {
await brain.add({
data: 'John Doe',
type: NounType.Person
})
const results = await brain.find({
query: 'learning',
type: NounType.Document
})
results.forEach(result => {
expect(result.entity.type).toBe(NounType.Document)
})
})
it('should support pagination', async () => {
const page1 = await brain.find({
query: 'learning',
limit: 1,
offset: 0
})
const page2 = await brain.find({
query: 'learning',
limit: 1,
offset: 1
})
expect(page1).toHaveLength(1)
expect(page2).toHaveLength(1)
expect(page1[0].entity.id).not.toBe(page2[0].entity.id)
})
})
describe('Batch Operations', () => {
it('should add multiple entities', async () => {
const result = await brain.addMany({
items: [
{
data: 'Document 1',
type: NounType.Document,
metadata: { index: 1 }
},
{
data: 'Document 2',
type: NounType.Document,
metadata: { index: 2 }
},
{
data: 'Document 3',
type: NounType.Document,
metadata: { index: 3 }
}
]
})
expect(result.successful).toHaveLength(3)
expect(result.failed).toHaveLength(0)
expect(result.total).toBe(3)
expect(result.duration).toBeGreaterThan(0)
})
it('should handle batch errors gracefully', async () => {
const result = await brain.addMany({
items: [
{
data: 'Valid document',
type: NounType.Document
},
{
data: null, // Invalid data to trigger error
type: NounType.Document
}
],
continueOnError: true
})
expect(result.successful).toHaveLength(1)
expect(result.failed).toHaveLength(1)
})
it('should delete multiple entities', async () => {
// Add test entities
const ids = await Promise.all([
brain.add({ data: 'Delete me 1', type: NounType.Document, metadata: { delete: true } }),
brain.add({ data: 'Delete me 2', type: NounType.Document, metadata: { delete: true } }),
brain.add({ data: 'Keep me', type: NounType.Document, metadata: { delete: false } })
])
const result = await brain.removeMany({
where: { delete: true }
})
expect(result.successful).toHaveLength(2)
expect(result.failed).toHaveLength(0)
// Verify entities were deleted
const remaining = await brain.get(ids[2])
expect(remaining).toBeDefined()
})
})
describe('Type Safety', () => {
it('should enforce NounType enum', async () => {
const id = await brain.add({
data: 'Test',
type: NounType.Document // Must use enum
})
const entity = await brain.get(id)
expect(entity!.type).toBe(NounType.Document)
})
it('should enforce VerbType enum', async () => {
const doc1 = await brain.add({ data: 'Doc 1', type: NounType.Document })
const doc2 = await brain.add({ data: 'Doc 2', type: NounType.Document })
await brain.relate({
from: doc1,
to: doc2,
type: VerbType.References // Must use enum
})
const relations = await brain.related({ from: doc1 })
expect(relations[0].type).toBe(VerbType.References)
})
})
describe('Error Handling', () => {
it('should handle missing entities gracefully', async () => {
const nonExistentId = '00000000-0000-0000-0000-000000000000'
const entity = await brain.get(nonExistentId)
expect(entity).toBeNull()
})
it('should require initialization before operations', async () => {
const uninitializedBrain = new Brainy({ requireSubtype: false })
await expect(uninitializedBrain.add({
data: 'Test',
type: NounType.Document
})).rejects.toThrow('not initialized')
})
it('should validate required parameters', async () => {
// @ts-expect-error - Testing validation
await expect(brain.add({})).rejects.toThrow()
})
})
describe('Configuration', () => {
it('should support custom configuration', async () => {
const customBrain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' },
model: { type: 'fast' },
cache: true,
warmup: false
})
await customBrain.init()
const id = await customBrain.add({
data: 'Test with custom config',
type: NounType.Document
})
expect(id).toBeTypeOf('string')
await customBrain.close()
})
})
})
describe('Brainy 3.0 Neural API', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' },
})
await brain.init()
})
afterEach(async () => {
await brain.close()
})
})

View file

@ -1,394 +0,0 @@
/**
* Brainy v3.0 Core API Test Suite
* Testing all core CRUD operations and basic functionality
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType, VerbType } from '../../src/types/graphTypes.js'
describe('Brainy v3.0 Core API Tests', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' }
})
await brain.init()
})
afterEach(async () => {
// Note: Brainy doesn't have shutdown() in v3, just let GC handle it
brain = null as any
})
describe('1. Add Operations', () => {
it('should add a simple text item', async () => {
const id = await brain.add({
data: 'Hello world',
type: NounType.Document
})
expect(id).toBeDefined()
expect(typeof id).toBe('string')
expect(id.length).toBeGreaterThan(0)
})
it('should add item with custom ID', async () => {
const customId = 'custom-123'
const id = await brain.add({
id: customId,
data: 'Custom ID test',
type: NounType.Document
})
expect(id).toBe(customId)
})
it('should add item with metadata', async () => {
const metadata = {
title: 'Test Doc',
category: 'testing',
score: 95.5,
tags: ['test', 'validation']
}
const id = await brain.add({
data: 'Document with metadata',
metadata,
type: NounType.Document
})
const retrieved = await brain.get(id)
expect(retrieved?.metadata).toEqual(metadata)
})
it('should add item with pre-computed vector', async () => {
const vector = new Array(384).fill(0).map(() => Math.random())
const id = await brain.add({
data: 'Pre-vectorized content',
vector,
type: NounType.Thing
})
const retrieved = await brain.get(id)
expect(retrieved).toBeDefined()
expect(retrieved?.vector).toHaveLength(384)
})
it('should handle concurrent adds', async () => {
const promises = Array(10).fill(0).map((_, i) =>
brain.add({
data: `Concurrent item ${i}`,
type: NounType.Document
})
)
const ids = await Promise.all(promises)
expect(ids).toHaveLength(10)
expect(new Set(ids).size).toBe(10) // All unique
})
it('should validate noun types', async () => {
// Valid type should work
const id = await brain.add({
data: 'Valid type',
type: NounType.Person
})
expect(id).toBeDefined()
// Invalid type should throw
await expect(brain.add({
data: 'Invalid type test',
type: 'InvalidType' as any
})).rejects.toThrow()
})
})
describe('2. Get Operations', () => {
let testId: string
beforeEach(async () => {
testId = await brain.add({
data: 'Test document for retrieval',
metadata: { test: true },
type: NounType.Document
})
})
it('should retrieve existing item by ID', async () => {
const item = await brain.get(testId)
expect(item).toBeDefined()
expect(item?.id).toBe(testId)
expect(item?.metadata?.test).toBe(true)
expect(item?.type).toBe(NounType.Document)
})
it('should return null for non-existent ID', async () => {
const item = await brain.get('non-existent-id')
expect(item).toBeNull()
})
it('should retrieve with vector included', async () => {
const item = await brain.get(testId)
expect(item?.vector).toBeDefined()
expect(item?.vector?.length).toBeGreaterThanOrEqual(384) // Default dimension
})
})
describe('3. Update Operations', () => {
let testId: string
beforeEach(async () => {
testId = await brain.add({
data: 'Original content',
metadata: { version: 1 },
type: NounType.Document
})
})
it('should update existing item data', async () => {
const success = await brain.update({
id: testId,
data: 'Updated content'
})
expect(success).toBe(true)
const updated = await brain.get(testId)
expect(updated).toBeDefined()
// Vector should be recalculated after content update
})
it('should update only metadata', async () => {
const success = await brain.update({
id: testId,
metadata: { version: 2, updated: true }
})
expect(success).toBe(true)
const updated = await brain.get(testId)
expect(updated?.metadata).toEqual({ version: 2, updated: true })
})
it('should update both data and metadata', async () => {
const success = await brain.update({
id: testId,
data: 'New content',
metadata: { version: 3 }
})
expect(success).toBe(true)
const updated = await brain.get(testId)
expect(updated?.metadata?.version).toBe(3)
})
it('should return false for non-existent ID', async () => {
const success = await brain.update({
id: 'non-existent',
data: 'Will fail'
})
expect(success).toBe(false)
})
})
describe('4. Delete Operations', () => {
it('should delete existing item', async () => {
const id = await brain.add({
data: 'To be deleted',
type: NounType.Document
})
const success = await brain.remove(id)
expect(success).toBe(true)
const item = await brain.get(id)
expect(item).toBeNull()
})
it('should return false for non-existent ID', async () => {
const success = await brain.remove('non-existent')
expect(success).toBe(false)
})
it('should handle concurrent deletes', async () => {
const ids = await Promise.all(
Array(5).fill(0).map(() =>
brain.add({ data: 'Concurrent delete test', type: NounType.Document })
)
)
await Promise.all(ids.map(id => brain.remove(id)))
// delete returns void, not boolean
// Verify all deleted
const items = await Promise.all(ids.map(id => brain.get(id)))
expect(items.every(item => item === null)).toBe(true)
})
})
describe('5. Relationship Operations', () => {
let entityA: string
let entityB: string
beforeEach(async () => {
entityA = await brain.add({ data: 'Entity A', type: NounType.Person })
entityB = await brain.add({ data: 'Entity B', type: NounType.Organization })
})
it('should create relationships between entities', async () => {
const verbId = await brain.relate({
from: entityA,
to: entityB,
type: VerbType.MemberOf,
metadata: { since: '2023' }
})
expect(verbId).toBeDefined()
expect(typeof verbId).toBe('string')
})
it('should retrieve relationships', async () => {
await brain.relate({
from: entityA,
to: entityB,
type: VerbType.Creates
})
const relations = await brain.related({ from: entityA })
expect(relations.length).toBeGreaterThanOrEqual(1)
expect(relations[0].from).toBe(entityA)
})
it('should delete relationships', async () => {
const verbId = await brain.relate({
from: entityA,
to: entityB,
type: VerbType.Owns
})
const success = await brain.unrelate(verbId)
expect(success).toBe(true)
const relations = await brain.related({ from: entityA })
const found = relations.find(r => r.id === verbId)
expect(found).toBeUndefined()
})
})
describe('6. Batch Operations', () => {
it('should add multiple items in batch', async () => {
const items = Array(10).fill(0).map((_, i) => ({
data: `Batch item ${i}`,
metadata: { index: i },
type: NounType.Document
}))
const result = await brain.addMany({ items })
expect(result.successful).toHaveLength(10)
expect(result.failed).toHaveLength(0)
expect(result.total).toBe(10)
})
it('should delete multiple items in batch', async () => {
// First add some items
const ids = await Promise.all(
Array(5).fill(0).map((_, i) =>
brain.add({ data: `Delete batch ${i}`, type: NounType.Document })
)
)
// Then delete them in batch
const result = await brain.removeMany({ ids })
expect(result.successful).toHaveLength(5)
expect(result.failed).toHaveLength(0)
})
it('should handle partial batch failures', async () => {
const items = [
{ data: 'Valid item', type: NounType.Document },
{ data: 'Invalid item', type: 'InvalidType' as any },
{ data: 'Another valid', type: NounType.Document }
]
const result = await brain.addMany({
items,
continueOnError: true
})
expect(result.successful.length).toBeGreaterThanOrEqual(2)
expect(result.failed.length).toBeGreaterThanOrEqual(1)
})
})
describe('7. Import/Export Operations', () => {
it('should export all data', async () => {
// Add some test data
const id1 = await brain.add({ data: 'Export test 1', type: NounType.Document })
const id2 = await brain.add({ data: 'Export test 2', type: NounType.Document })
await brain.relate({ from: id1, to: id2, type: VerbType.References })
const exported = await brain.export()
expect(exported).toBeDefined()
expect(exported.entities).toBeDefined()
expect(exported.relationships).toBeDefined()
expect(exported.metadata).toBeDefined()
})
it('should import data', async () => {
// Create export data
const exportData = {
entities: [
{ id: 'imp-1', data: 'Imported 1', type: NounType.Document, metadata: {} },
{ id: 'imp-2', data: 'Imported 2', type: NounType.Document, metadata: {} }
],
relationships: [],
metadata: { version: '3.0.0' }
}
await brain.import(exportData)
// Verify imported data
const item1 = await brain.get('imp-1')
const item2 = await brain.get('imp-2')
expect(item1).toBeDefined()
expect(item2).toBeDefined()
})
})
describe('8. Statistics and Insights', () => {
beforeEach(async () => {
// Add some test data
await brain.add({ data: 'Doc 1', type: NounType.Document })
await brain.add({ data: 'Person 1', type: NounType.Person })
await brain.add({ data: 'Org 1', type: NounType.Organization })
})
it('should provide insights', async () => {
const insights = await brain.insights()
expect(insights).toBeDefined()
expect(insights.entities).toBeGreaterThanOrEqual(3)
expect(insights.types).toBeDefined()
expect(Object.keys(insights.types).length).toBeGreaterThan(0)
})
it('should suggest relevant queries', async () => {
const suggestions = await brain.suggest({ limit: 3 })
expect(suggestions).toBeDefined()
expect(suggestions.queries).toBeDefined()
expect(Array.isArray(suggestions.queries)).toBe(true)
expect(suggestions.queries.length).toBeLessThanOrEqual(3)
})
})
})

View file

@ -1,367 +0,0 @@
/**
* Brainy v3.0 Find & Triple Intelligence Test Suite
* Testing vector search, metadata filtering, and fusion strategies
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType, VerbType } from '../../src/types/graphTypes.js'
describe('Find and Triple Intelligence', () => {
let brain: Brainy
let testData: any[]
beforeAll(async () => {
brain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' }
})
await brain.init()
// Add diverse test data
testData = [
// Technology items
{ data: 'JavaScript is a programming language for web development', metadata: { category: 'tech', language: 'javascript', difficulty: 'medium' }, type: NounType.Document },
{ data: 'TypeScript adds static typing to JavaScript', metadata: { category: 'tech', language: 'typescript', difficulty: 'advanced' }, type: NounType.Document },
{ data: 'Python is great for data science and AI', metadata: { category: 'tech', language: 'python', difficulty: 'easy' }, type: NounType.Document },
{ data: 'React is a JavaScript library for building UIs', metadata: { category: 'tech', framework: 'react', language: 'javascript' }, type: NounType.Document },
// Science items
{ data: 'Quantum computing uses quantum mechanics principles', metadata: { category: 'science', field: 'physics', complexity: 'high' }, type: NounType.Concept },
{ data: 'Machine learning enables computers to learn from data', metadata: { category: 'science', field: 'ai', complexity: 'medium' }, type: NounType.Concept },
{ data: 'Neural networks are inspired by biological brains', metadata: { category: 'science', field: 'ai', complexity: 'high' }, type: NounType.Concept },
// Business items
{ data: 'Market analysis helps understand consumer behavior', metadata: { category: 'business', domain: 'marketing', importance: 'high' }, type: NounType.Process },
{ data: 'Project management ensures successful delivery', metadata: { category: 'business', domain: 'management', importance: 'critical' }, type: NounType.Process },
{ data: 'Financial planning is essential for growth', metadata: { category: 'business', domain: 'finance', importance: 'critical' }, type: NounType.Process }
]
// Add all test data
for (const item of testData) {
await brain.add(item)
}
})
afterAll(() => {
brain = null as any
})
describe('Vector Search', () => {
it('should find similar items by text query', async () => {
const results = await brain.find({
query: 'JavaScript programming',
limit: 3
})
expect(results).toHaveLength(3)
expect(results[0].score).toBeGreaterThan(0)
expect(results[0].score).toBeLessThanOrEqual(1)
// Should find JavaScript-related items first
const topResult = results[0].entity
expect(topResult.metadata?.language).toBeDefined()
})
it('should respect limit parameter', async () => {
const results = await brain.find({
query: 'technology',
limit: 5
})
expect(results.length).toBeLessThanOrEqual(5)
})
it('should find by vector directly', async () => {
// Get vector from an existing item
const jsItem = await brain.find({ query: 'JavaScript', limit: 1 })
const vector = jsItem[0].entity.vector
const results = await brain.find({
vector,
limit: 3
})
expect(results).toHaveLength(3)
// First result should be very similar (same or nearly same vector)
expect(results[0].score).toBeGreaterThan(0.9)
})
it('should return all items when no query provided', async () => {
const results = await brain.find({})
expect(results.length).toBeGreaterThanOrEqual(testData.length)
})
})
describe('Metadata Filtering', () => {
it('should filter by single metadata field', async () => {
const results = await brain.find({
where: { category: 'tech' }
})
expect(results.length).toBeGreaterThan(0)
results.forEach(r => {
expect(r.entity.metadata?.category).toBe('tech')
})
})
it('should filter by multiple metadata fields', async () => {
const results = await brain.find({
where: {
category: 'tech',
language: 'javascript'
}
})
results.forEach(r => {
expect(r.entity.metadata?.category).toBe('tech')
expect(r.entity.metadata?.language).toBe('javascript')
})
})
it('should support $gte operator', async () => {
const results = await brain.find({
where: {
importance: { $gte: 'high' }
}
})
results.forEach(r => {
const importance = r.entity.metadata?.importance
if (importance) {
expect(['high', 'critical']).toContain(importance)
}
})
})
it('should support $contains operator for arrays', async () => {
// Add item with array metadata
await brain.add({
data: 'Test with tags',
metadata: { tags: ['test', 'validation', 'qa'] },
type: NounType.Document
})
const results = await brain.find({
where: {
tags: { $contains: 'test' }
}
})
const found = results.find(r =>
Array.isArray(r.entity.metadata?.tags) &&
r.entity.metadata.tags.includes('test')
)
expect(found).toBeDefined()
})
it('should handle complex nested filters', async () => {
const results = await brain.find({
where: {
$or: [
{ category: 'tech', language: 'javascript' },
{ category: 'science', field: 'ai' }
]
}
})
results.forEach(r => {
const meta = r.entity.metadata
const matchesTech = meta?.category === 'tech' && meta?.language === 'javascript'
const matchesScience = meta?.category === 'science' && meta?.field === 'ai'
expect(matchesTech || matchesScience).toBe(true)
})
})
})
describe('Type Filtering', () => {
it('should filter by single noun type', async () => {
const results = await brain.find({
type: NounType.Document
})
results.forEach(r => {
expect(r.entity.type).toBe(NounType.Document)
})
})
it('should filter by multiple noun types', async () => {
const results = await brain.find({
type: [NounType.Document, NounType.Concept]
})
results.forEach(r => {
expect([NounType.Document, NounType.Concept]).toContain(r.entity.type)
})
})
})
describe('Combined Search (Triple Intelligence)', () => {
it('should combine vector search with metadata filtering', async () => {
const results = await brain.find({
query: 'programming',
where: { category: 'tech' },
limit: 5
})
expect(results.length).toBeGreaterThan(0)
results.forEach(r => {
expect(r.entity.metadata?.category).toBe('tech')
})
})
it('should combine vector, metadata, and type filtering', async () => {
const results = await brain.find({
query: 'artificial intelligence',
where: { category: 'science' },
type: NounType.Concept,
limit: 5
})
results.forEach(r => {
expect(r.entity.metadata?.category).toBe('science')
expect(r.entity.type).toBe(NounType.Concept)
})
})
it('should support fusion strategies', async () => {
const results = await brain.find({
query: 'JavaScript',
where: { category: 'tech' },
fusion: {
strategy: 'adaptive',
weights: { vector: 0.7, field: 0.3 }
}
})
expect(results).toBeDefined()
expect(results.length).toBeGreaterThan(0)
})
it('should handle fusion with graph intelligence', async () => {
// Create some relationships
const items = await brain.find({ limit: 3 })
if (items.length >= 2) {
await brain.relate({
from: items[0].id,
to: items[1].id,
type: VerbType.References
})
}
const results = await brain.find({
query: 'technology',
connected: { to: items[0]?.id },
fusion: {
strategy: 'adaptive',
weights: { vector: 0.5, graph: 0.3, field: 0.2 }
}
})
expect(results).toBeDefined()
})
})
describe('Performance', () => {
it('should handle large result sets efficiently', async () => {
const start = Date.now()
const results = await brain.find({ limit: 100 })
const elapsed = Date.now() - start
expect(elapsed).toBeLessThan(1000) // Should complete in under 1 second
expect(results.length).toBeLessThanOrEqual(100)
})
it('should cache repeated searches', async () => {
const query = { query: 'caching test', limit: 5 }
// First search
const results1 = await brain.find(query)
// Second search (should hit cache)
const results2 = await brain.find(query)
expect(results1).toEqual(results2)
// Note: Cache might not always be faster in tests due to overhead
// But results should be identical
})
})
describe('Edge Cases', () => {
it('should handle empty query gracefully', async () => {
const results = await brain.find({ query: '' })
expect(results).toBeDefined()
expect(Array.isArray(results)).toBe(true)
})
it('should handle non-matching filters', async () => {
const results = await brain.find({
where: { nonExistentField: 'value' }
})
expect(results).toBeDefined()
expect(Array.isArray(results)).toBe(true)
})
it('should handle very long queries', async () => {
const longQuery = 'test '.repeat(1000) // 5000 characters
const results = await brain.find({ query: longQuery, limit: 1 })
expect(results).toBeDefined()
expect(Array.isArray(results)).toBe(true)
})
it('should handle special characters in queries', async () => {
const specialQuery = '!@#$%^&*()_+-=[]{}|;\':",./<>?'
const results = await brain.find({ query: specialQuery, limit: 1 })
expect(results).toBeDefined()
expect(Array.isArray(results)).toBe(true)
})
it('should handle unicode in queries', async () => {
const unicodeQuery = '你好世界 🌍 مرحبا بالعالم'
const results = await brain.find({ query: unicodeQuery, limit: 1 })
expect(results).toBeDefined()
expect(Array.isArray(results)).toBe(true)
})
})
describe('Similarity Search', () => {
it('should find similar items to a given ID', async () => {
const items = await brain.find({ limit: 1 })
if (items.length > 0) {
const results = await brain.similar({
to: items[0].id,
limit: 3
})
expect(results).toHaveLength(3)
// First result might be the item itself
results.forEach(r => {
expect(r.score).toBeGreaterThanOrEqual(0)
expect(r.score).toBeLessThanOrEqual(1)
})
}
})
it('should exclude the source item from similar results', async () => {
const items = await brain.find({ limit: 1 })
if (items.length > 0) {
const results = await brain.similar({
to: items[0].id,
limit: 5
})
// Check if source is excluded (implementation dependent)
const selfMatch = results.find(r => r.id === items[0].id)
if (selfMatch) {
// If included, should be first with score ~1
expect(results[0].id).toBe(items[0].id)
expect(results[0].score).toBeGreaterThan(0.99)
}
}
})
})
})

View file

@ -1,330 +0,0 @@
/**
* Streaming Pipeline Tests
* Tests for the new streaming data pipeline system
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Pipeline, createPipeline } from '../src/streaming/pipeline.js'
import { Brainy } from '../src/brainy.js'
import { NounType } from '../src/types/graphTypes.js'
describe('Streaming Pipeline', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy({ requireSubtype: false,
storage: { type: 'memory' },
warmup: false
})
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('Core Operations', () => {
it('should process data through pipeline', async () => {
const results: number[] = []
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 5; i++) {
yield i
}
})
.map(x => x * 2)
.filter(x => x > 4)
.sink(x => { results.push(x) })
.run()
expect(results).toEqual([6, 8, 10])
})
it('should handle async transformations', async () => {
const results: string[] = []
await new Pipeline()
.source(async function* () {
yield 'hello'
yield 'world'
})
.map(async (text) => {
await new Promise(resolve => setTimeout(resolve, 10))
return text.toUpperCase()
})
.sink(x => { results.push(x) })
.run()
expect(results).toEqual(['HELLO', 'WORLD'])
})
it('should batch items', async () => {
const batches: number[][] = []
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 10; i++) {
yield i
}
})
.batch(3)
.sink(batch => { batches.push(batch) })
.run()
expect(batches).toEqual([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10]
])
})
it('should collect all results', async () => {
const pipeline = new Pipeline()
.source(async function* () {
for (let i = 1; i <= 5; i++) {
yield i
}
})
.map(x => x * x)
const results = await pipeline.collect()
expect(results).toEqual([1, 4, 9, 16, 25])
})
})
describe('Window Operations', () => {
it('should support tumbling windows', async () => {
const windows: number[][] = []
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 10; i++) {
yield i
}
})
.window(3, 'tumbling')
.sink(window => { windows.push(window) })
.run()
expect(windows).toEqual([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10]
])
})
it('should support sliding windows', async () => {
const windows: number[][] = []
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 5; i++) {
yield i
}
})
.window(3, 'sliding')
.sink(window => { windows.push([...window]) })
.run()
expect(windows).toEqual([
[1, 2, 3],
[2, 3, 4],
[3, 4, 5]
])
})
})
describe('Reduce Operations', () => {
it('should reduce values', async () => {
let result = 0
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 5; i++) {
yield i
}
})
.reduce((acc, val) => acc + val, 0)
.sink(sum => { result = sum })
.run()
expect(result).toBe(15)
})
it('should work with complex reducers', async () => {
interface Stats {
count: number
sum: number
max: number
}
let stats: Stats = { count: 0, sum: 0, max: 0 }
await new Pipeline<number>()
.source(async function* () {
for (let i = 1; i <= 10; i++) {
yield i
}
})
.reduce<Stats>((acc, val) => ({
count: acc.count + 1,
sum: acc.sum + val,
max: Math.max(acc.max, val)
}), { count: 0, sum: 0, max: 0 })
.sink(s => { stats = s })
.run()
expect(stats).toEqual({
count: 10,
sum: 55,
max: 10
})
})
})
describe('Brainy Integration', () => {
it('should sink data to Brainy', async () => {
const pipeline = new Pipeline(brain)
.source(async function* () {
yield { content: 'Document 1' }
yield { content: 'Document 2' }
yield { content: 'Document 3' }
})
.toBrainy({
type: NounType.Document,
metadata: { source: 'pipeline' },
batchSize: 2
})
await pipeline.run()
// Verify data was added
const results = await brain.find({
where: { 'metadata.source': 'pipeline' }
})
expect(results.length).toBe(3)
})
it('should process and transform before storing', async () => {
const pipeline = new Pipeline(brain)
.source(async function* () {
yield 'hello world'
yield 'goodbye world'
})
.map(text => ({
content: text,
processed: text.toUpperCase()
}))
.toBrainy({
type: NounType.Document,
metadata: { pipeline: true }
})
await pipeline.run()
const results = await brain.find({
where: { 'metadata.pipeline': true }
})
expect(results.length).toBe(2)
})
})
describe('Error Handling', () => {
it('should handle errors with handler', async () => {
const errors: Error[] = []
await new Pipeline()
.source(async function* () {
yield 1
yield 2
yield 3
})
.map(x => {
if (x === 2) throw new Error('Test error')
return x
})
.sink(() => {})
.run({
errorHandler: (error) => errors.push(error)
})
expect(errors.length).toBe(1)
expect(errors[0].message).toBe('Test error')
})
it('should stop on abort signal', async () => {
let count = 0
const pipeline = new Pipeline()
.source(async function* () {
for (let i = 1; i <= 100; i++) {
yield i
}
})
.sink(() => { count++ })
// Start pipeline
const runPromise = pipeline.run()
// Stop after a short delay
setTimeout(() => pipeline.stop(), 50)
await runPromise
// Should have processed some but not all items
expect(count).toBeGreaterThan(0)
expect(count).toBeLessThan(100)
})
})
describe('Performance Features', () => {
it('should throttle sink operations', async () => {
const times: number[] = []
const startTime = Date.now()
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 3; i++) {
yield i
}
})
.throttledSink(
() => { times.push(Date.now() - startTime) },
10 // 10 ops/sec max
)
.run()
// Check that operations were throttled
expect(times.length).toBe(3)
expect(times[2] - times[0]).toBeGreaterThanOrEqual(200) // At least 200ms for 3 items at 10/sec
})
it('should run monitoring', async () => {
const logs: string[] = []
const originalLog = console.log
console.log = (msg: string) => logs.push(msg)
try {
await new Pipeline()
.source(async function* () {
for (let i = 1; i <= 10; i++) {
yield i
}
})
.sink(() => {})
.run({ monitoring: true })
// Should have logged completion metrics
expect(logs.some(log => log.includes('Pipeline completed'))).toBe(true)
expect(logs.some(log => log.includes('Throughput'))).toBe(true)
} finally {
console.log = originalLog
}
})
})
})

View file

@ -1,368 +0,0 @@
/**
* TypeAware + Transactions Integration Tests
*
* Verifies that transactions work correctly with type-aware storage:
* - Type-specific routing
* - Type cache updates
* - Type changes during updates
* - Per-type performance optimization
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../../src/brainy.js'
import { NounType, VerbType } from '../../../src/types/graphTypes.js'
import { tmpdir } from 'os'
import { join } from 'path'
import { mkdirSync, rmSync } from 'fs'
describe('Transactions + TypeAware Storage Integration', () => {
let brain: Brainy
let testDir: string
beforeEach(async () => {
testDir = join(tmpdir(), `brainy-typeaware-test-${Date.now()}`)
mkdirSync(testDir, { recursive: true })
brain = new Brainy({ requireSubtype: false,
storage: {
type: 'filesystem',
path: testDir
}
})
await brain.init()
})
afterEach(async () => {
if (brain) {
await brain.shutdown()
}
if (testDir) {
rmSync(testDir, { recursive: true, force: true })
}
})
describe('Type-Specific Routing', () => {
it('should route entities to type-specific storage atomically', async () => {
// Add entities of different types
const personId = await brain.add({
data: { name: 'John Doe', role: 'Developer' },
type: NounType.Person
})
const orgId = await brain.add({
data: { name: 'Acme Corp', industry: 'Tech' },
type: NounType.Organization
})
const placeId = await brain.add({
data: { name: 'San Francisco', country: 'USA' },
type: NounType.Place
})
// Verify all entities stored with correct types
const person = await brain.get(personId)
const org = await brain.get(orgId)
const place = await brain.get(placeId)
expect(person?.type).toBe(NounType.Person)
expect(org?.type).toBe(NounType.Organization)
expect(place?.type).toBe(NounType.Place)
})
it('should handle type-specific queries atomically', async () => {
// Add multiple entities of same type
await brain.add({
data: { name: 'Alice', role: 'Engineer' },
type: NounType.Person
})
await brain.add({
data: { name: 'Bob', role: 'Designer' },
type: NounType.Person
})
await brain.add({
data: { name: 'Acme', industry: 'Tech' },
type: NounType.Organization
})
// Query by type
const results = await brain.find({
filter: { type: NounType.Person }
})
expect(results).toHaveLength(2)
expect(results.every(r => r.type === NounType.Person)).toBe(true)
})
})
describe('Type Changes During Updates', () => {
it('should handle type changes atomically in update operations', async () => {
// Add entity as Person
const id = await brain.add({
data: { name: 'John Smith', category: 'individual' },
type: NounType.Person
})
// Verify initial type
let entity = await brain.get(id)
expect(entity?.type).toBe(NounType.Person)
// Update to Organization (type change)
await brain.update({
id,
type: NounType.Organization,
data: { name: 'Smith Corp', category: 'business' }
})
// Verify type changed
entity = await brain.get(id)
expect(entity?.type).toBe(NounType.Organization)
expect(entity?.data.category).toBe('business')
})
it('should rollback type changes on failed update', async () => {
// Add entity as Person
const id = await brain.add({
data: { name: 'Jane Doe' },
type: NounType.Person
})
// Attempt to update with invalid data (will fail)
let failed = false
try {
await brain.update({
id,
type: NounType.Organization,
data: null as any // Invalid
})
} catch (e) {
failed = true
}
expect(failed).toBe(true)
// Type should remain Person (rollback)
const entity = await brain.get(id)
expect(entity?.type).toBe(NounType.Person)
expect(entity?.data.name).toBe('Jane Doe')
})
})
describe('Type Cache Consistency', () => {
it('should maintain type cache consistency during transactions', async () => {
// Add multiple entities
const ids: string[] = []
const types = [
NounType.Person,
NounType.Organization,
NounType.Place,
NounType.Event,
NounType.Concept
]
for (let i = 0; i < types.length; i++) {
const id = await brain.add({
data: { name: `Entity ${i}`, index: i },
type: types[i]
})
ids.push(id)
}
// Verify all types cached correctly
for (let i = 0; i < ids.length; i++) {
const entity = await brain.get(ids[i])
expect(entity?.type).toBe(types[i])
}
})
it('should update type cache on type changes', async () => {
const id = await brain.add({
data: { name: 'Initial' },
type: NounType.Thing
})
// Sequence of type changes
const typeSequence = [
NounType.Concept,
NounType.Event,
NounType.Organization,
NounType.Person
]
for (const newType of typeSequence) {
await brain.update({
id,
type: newType,
data: { name: `As ${newType}` }
})
const entity = await brain.get(id)
expect(entity?.type).toBe(newType)
}
})
})
describe('Per-Type Performance', () => {
it('should handle large numbers of same-type entities efficiently', async () => {
const startTime = Date.now()
// Add 50 entities of same type (type-aware storage optimization)
const ids: string[] = []
for (let i = 0; i < 50; i++) {
const id = await brain.add({
data: { name: `Person ${i}`, index: i },
type: NounType.Person
})
ids.push(id)
}
const addTime = Date.now() - startTime
// Verify all entities stored
const retrieveStart = Date.now()
for (const id of ids) {
const entity = await brain.get(id)
expect(entity).toBeTruthy()
}
const retrieveTime = Date.now() - retrieveStart
// Type-aware storage should be reasonably fast
expect(addTime).toBeLessThan(5000) // 5 seconds for 50 adds
expect(retrieveTime).toBeLessThan(2000) // 2 seconds for 50 retrieves
})
})
describe('Mixed Type Operations', () => {
it('should handle operations across multiple types atomically', async () => {
// Create entities of different types
const personId = await brain.add({
data: { name: 'Alice' },
type: NounType.Person
})
const orgId = await brain.add({
data: { name: 'TechCorp' },
type: NounType.Organization
})
const eventId = await brain.add({
data: { name: 'Conference 2024' },
type: NounType.Event
})
// Create relationships across types (atomic)
await brain.relate({
from: personId,
to: orgId,
type: VerbType.WorksFor
})
await brain.relate({
from: personId,
to: eventId,
type: VerbType.Attends
})
await brain.relate({
from: orgId,
to: eventId,
type: VerbType.Sponsors
})
// Verify relationships exist
const personRelations = await brain.related({ from: personId })
const orgRelations = await brain.related({ from: orgId })
expect(personRelations).toHaveLength(2)
expect(orgRelations).toHaveLength(1)
})
it('should handle delete with cascade across types', async () => {
// Create multi-type graph
const personId = await brain.add({
data: { name: 'Bob' },
type: NounType.Person
})
const projectId = await brain.add({
data: { name: 'Project X' },
type: NounType.Thing
})
const taskId = await brain.add({
data: { name: 'Task 1' },
type: NounType.Thing
})
// Create relationships
await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn
})
await brain.relate({
from: projectId,
to: taskId,
type: VerbType.Contains
})
// Delete person (should cascade delete relationships)
await brain.remove(personId)
// Verify person deleted
const person = await brain.get(personId)
expect(person).toBeNull()
// Verify project and task still exist (different types)
const project = await brain.get(projectId)
const task = await brain.get(taskId)
expect(project).toBeTruthy()
expect(task).toBeTruthy()
// Verify relationships from person are deleted
const relations = await brain.related({ from: personId })
expect(relations).toHaveLength(0)
})
})
describe('Type Validation', () => {
it('should validate types during atomic operations', async () => {
// Valid type - should succeed
const id = await brain.add({
data: { name: 'Test' },
type: NounType.Person
})
expect(id).toBeTruthy()
// Verify type stored correctly
const entity = await brain.get(id)
expect(entity?.type).toBe(NounType.Person)
})
it('should handle type-specific metadata atomically', async () => {
// Add with type-specific metadata
const personId = await brain.add({
data: { name: 'Charlie', age: 30, occupation: 'Engineer' },
type: NounType.Person,
metadata: { verified: true, source: 'HR' }
})
const orgId = await brain.add({
data: { name: 'StartupCo', employees: 50, founded: 2020 },
type: NounType.Organization,
metadata: { verified: false, source: 'Registration' }
})
// Verify metadata with type context
const person = await brain.get(personId)
const org = await brain.get(orgId)
expect(person?.metadata?.verified).toBe(true)
expect(org?.metadata?.verified).toBe(false)
})
})
})

View file

@ -0,0 +1,69 @@
/**
* @module tests/unit/test-suite-coverage-guard
* @description Prevents a test file from silently falling outside EVERY vitest
* config (so it never runs and gives false coverage confidence the exact drift
* that left ~27 test files un-run before 8.0). Every `*.test.ts` must either match
* a gate config (`tests/unit/**`, `tests/integration/**`, `*.unit.test.ts`,
* `*.integration.test.ts`) or be explicitly listed in MANUAL_ONLY below.
*/
import { describe, it, expect } from 'vitest'
import { readdirSync } from 'node:fs'
import { join, relative, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '../..')
const testsDir = join(repoRoot, 'tests')
function allTestFiles(dir: string, out: string[] = []): string[] {
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name)
if (e.isDirectory()) allTestFiles(p, out)
else if (e.isFile() && p.endsWith('.test.ts')) out.push(relative(repoRoot, p).split('\\').join('/'))
}
return out
}
/**
* Test files INTENTIONALLY excluded from the unit/integration gate: benchmarks,
* scale/perf measurements, package-size checks, and real-model-load checks. They
* are run manually (slow / need real resources), not in CI. Every entry is a
* conscious decision a NEW orphan not listed here fails the guard below.
*/
const MANUAL_ONLY = new Set<string>([
'tests/api/performance-benchmarks.test.ts',
'tests/critical-neural-validation.test.ts',
'tests/critical-performance-benchmark.test.ts',
'tests/model-loading.test.ts',
'tests/package-size-breakdown.test.ts',
'tests/package-size-limit.test.ts',
'tests/performance/graph-scale-performance.test.ts',
'tests/performance/triple-intelligence-scale.test.ts',
'tests/performance/typeAware.bench.test.ts'
])
function inGate(rel: string): boolean {
return (
rel.startsWith('tests/unit/') ||
rel.startsWith('tests/integration/') ||
rel.endsWith('.unit.test.ts') ||
rel.endsWith('.integration.test.ts')
)
}
describe('test-suite coverage guard', () => {
it('every *.test.ts runs in a gate config or is explicitly allowlisted as manual', () => {
const orphans = allTestFiles(testsDir).filter((f) => !inGate(f) && !MANUAL_ONLY.has(f))
expect(
orphans,
'These test files match NO vitest config and are not in MANUAL_ONLY — rename to ' +
'*.unit.test.ts / *.integration.test.ts (or move under tests/unit|integration), or add to ' +
`MANUAL_ONLY if they are benchmarks:\n${orphans.join('\n')}`
).toEqual([])
})
it('the manual allowlist has no stale entries (every listed file still exists)', () => {
const all = new Set(allTestFiles(testsDir))
const stale = [...MANUAL_ONLY].filter((f) => !all.has(f))
expect(stale, `MANUAL_ONLY lists files that no longer exist — remove them:\n${stale.join('\n')}`).toEqual([])
})
})

View file

@ -1,344 +0,0 @@
/**
* Test VFS Graph Relationships
*
* Verifies that VFS properly uses Brainy's graph relationships
* instead of metadata-based path queries
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
import { Brainy } from '../../src/brainy.js'
import { VerbType } from '../../src/types/graphTypes.js'
describe('VFS Graph Relationships', () => {
let vfs: VirtualFileSystem
let brain: Brainy
beforeEach(async () => {
brain = new Brainy({ requireSubtype: false })
await brain.init()
vfs = new VirtualFileSystem(brain)
await vfs.init()
})
it('should use proper graph relationships for directory structure', async () => {
// Create a directory structure
await vfs.mkdir('/projects')
await vfs.mkdir('/projects/brainy')
await vfs.writeFile('/projects/brainy/README.md', 'Test content')
await vfs.writeFile('/projects/brainy/package.json', '{}')
// Get the entity IDs
const projectsId = await vfs.resolvePath('/projects')
const brainyId = await vfs.resolvePath('/projects/brainy')
const readmeId = await vfs.resolvePath('/projects/brainy/README.md')
// Verify relationships are created properly
const projectRelations = await brain.related({
from: projectsId,
type: VerbType.Contains
})
expect(projectRelations).toHaveLength(1)
expect(projectRelations[0].to).toBe(brainyId)
// Verify brainy directory contains its files
const brainyRelations = await brain.related({
from: brainyId,
type: VerbType.Contains
})
expect(brainyRelations).toHaveLength(2)
const childIds = brainyRelations.map(r => r.to)
expect(childIds).toContain(readmeId)
})
it('should traverse directory tree using relationships', async () => {
// Create nested structure
await vfs.mkdir('/a')
await vfs.mkdir('/a/b')
await vfs.mkdir('/a/b/c')
await vfs.writeFile('/a/b/c/file.txt', 'deep file')
// Read directory using relationships
const contents = await vfs.readdir('/a/b/c')
expect(contents).toContain('file.txt')
// Verify path resolution uses graph traversal
const fileId = await vfs.resolvePath('/a/b/c/file.txt')
const fileEntity = await brain.get(fileId)
expect(fileEntity?.metadata?.name).toBe('file.txt')
})
it('should properly handle custom relationships between files', async () => {
// Create two files
await vfs.writeFile('/doc1.md', 'Document 1')
await vfs.writeFile('/doc2.md', 'Document 2')
// Add a custom relationship
await vfs.addRelationship('/doc1.md', '/doc2.md', VerbType.References)
// Get relationships using proper graph API
const relationships = await vfs.getRelationships('/doc1.md')
// Should find the reference relationship
const refRelation = relationships.find(r => r.type === VerbType.References)
expect(refRelation).toBeDefined()
// Verify it's using actual graph relationships, not metadata
const doc1Id = await vfs.resolvePath('/doc1.md')
const doc2Id = await vfs.resolvePath('/doc2.md')
const directRelations = await brain.related({
from: doc1Id,
type: VerbType.References
})
expect(directRelations).toHaveLength(1)
expect(directRelations[0].to).toBe(doc2Id)
})
it('should not fall back to metadata path queries', async () => {
// Create a file
await vfs.writeFile('/test.txt', 'test')
// Get the entity
const entity = await vfs.getEntity('/test.txt')
// Verify the entity has proper metadata
expect(entity.metadata.path).toBe('/test.txt')
expect(entity.metadata.name).toBe('test.txt')
// But the parent relationship should be through graph, not metadata
const rootId = await vfs.resolvePath('/')
const testId = entity.id
// Check that root contains test.txt via relationships
const rootRelations = await brain.related({
from: rootId,
type: VerbType.Contains
})
expect(rootRelations.some(r => r.to === testId)).toBe(true)
})
it('should efficiently query children using relationships', async () => {
// Create many files in a directory
await vfs.mkdir('/many')
for (let i = 0; i < 10; i++) {
await vfs.writeFile(`/many/file${i}.txt`, `content ${i}`)
}
// Get directory contents
const files = await vfs.readdir('/many')
expect(files).toHaveLength(10)
// Verify it's using relationships, not metadata queries
const manyId = await vfs.resolvePath('/many')
const relations = await brain.related({
from: manyId,
type: VerbType.Contains
})
expect(relations).toHaveLength(10)
})
it('should handle moving files by updating relationships', async () => {
// Create source structure
await vfs.mkdir('/source')
await vfs.writeFile('/source/file.txt', 'content')
await vfs.mkdir('/dest')
// Move file
await vfs.move('/source/file.txt', '/dest/file.txt')
// Verify relationships are updated
const sourceId = await vfs.resolvePath('/source')
const destId = await vfs.resolvePath('/dest')
const fileId = await vfs.resolvePath('/dest/file.txt')
// Source should not contain file anymore
const sourceRelations = await brain.related({
from: sourceId,
type: VerbType.Contains
})
expect(sourceRelations).toHaveLength(0)
// Dest should contain file
const destRelations = await brain.related({
from: destId,
type: VerbType.Contains
})
expect(destRelations).toHaveLength(1)
expect(destRelations[0].to).toBe(fileId)
})
it('should support complex graph queries', async () => {
// Create interconnected structure
await vfs.mkdir('/docs')
await vfs.writeFile('/docs/main.md', 'Main doc')
await vfs.writeFile('/docs/related1.md', 'Related 1')
await vfs.writeFile('/docs/related2.md', 'Related 2')
// Add cross-references
await vfs.addRelationship('/docs/main.md', '/docs/related1.md', VerbType.References)
await vfs.addRelationship('/docs/main.md', '/docs/related2.md', VerbType.References)
await vfs.addRelationship('/docs/related1.md', '/docs/related2.md', VerbType.References)
// Get all related documents
const related = await vfs.getRelated('/docs/main.md')
// Should find parent (Contains) and references
const references = related.filter(r => r.direction === 'from')
expect(references.length).toBeGreaterThanOrEqual(2)
})
it('should always create Contains relationship when writing files', async () => {
// This test verifies the fix for the critical bug where writeFile()
// was not creating Contains relationships for updated files
// Test 1: New file should have Contains relationship
await vfs.writeFile('/test-new.txt', 'Hello World')
const rootId = await vfs.resolvePath('/')
const fileEntityId = await vfs.resolvePath('/test-new.txt')
// Check that Contains relationship exists
const relations = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
expect(relations).toHaveLength(1)
expect(relations[0].type).toBe(VerbType.Contains)
// Verify readdir returns the file
const files = await vfs.readdir('/')
expect(files).toContain('test-new.txt')
// Test 2: Updated file should maintain Contains relationship
await vfs.writeFile('/test-new.txt', 'Updated content')
// Relationship should still exist after update
const relationsAfterUpdate = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
expect(relationsAfterUpdate).toHaveLength(1)
// readdir should still work
const filesAfterUpdate = await vfs.readdir('/')
expect(filesAfterUpdate).toContain('test-new.txt')
// Test 3: Multiple updates should not create duplicate relationships
await vfs.writeFile('/test-new.txt', 'Another update')
await vfs.writeFile('/test-new.txt', 'Yet another update')
const finalRelations = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
// Should still have exactly one Contains relationship
expect(finalRelations).toHaveLength(1)
})
it('should repair missing Contains relationships on file update', async () => {
// This test simulates a scenario where a file exists but its Contains
// relationship is missing (could happen due to corruption or bugs)
// Create a file normally first
await vfs.writeFile('/orphan-test.txt', 'Initial content')
const rootId = await vfs.resolvePath('/')
const fileEntityId = await vfs.resolvePath('/orphan-test.txt')
// Manually delete the Contains relationship to simulate the bug
const initialRelations = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
// Delete the relationship (simulating the corruption/bug)
for (const rel of initialRelations) {
await brain.unrelate(rel.id)
}
// Verify the relationship is gone
const brokenRelations = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
expect(brokenRelations).toHaveLength(0)
// readdir should fail to find the file (the bug symptom)
const brokenList = await vfs.readdir('/')
expect(brokenList).not.toContain('orphan-test.txt')
// Now update the file - this should repair the missing relationship
await vfs.writeFile('/orphan-test.txt', 'Fixed content')
// Verify the relationship is restored
const fixedRelations = await brain.related({
from: rootId,
to: fileEntityId,
type: VerbType.Contains
})
expect(fixedRelations).toHaveLength(1)
expect(fixedRelations[0].type).toBe(VerbType.Contains)
// readdir should now work again
const fixedList = await vfs.readdir('/')
expect(fixedList).toContain('orphan-test.txt')
})
it('should create Contains relationships for files in nested directories', async () => {
// Test that the fix works for nested directory structures
await vfs.mkdir('/level1')
await vfs.mkdir('/level1/level2')
await vfs.mkdir('/level1/level2/level3')
// Write a file deep in the structure
await vfs.writeFile('/level1/level2/level3/deep.txt', 'Deep content')
// Get entity IDs
const level3Id = await vfs.resolvePath('/level1/level2/level3')
const fileEntityId = await vfs.resolvePath('/level1/level2/level3/deep.txt')
// Verify Contains relationship exists
const relations = await brain.related({
from: level3Id,
to: fileEntityId,
type: VerbType.Contains
})
expect(relations).toHaveLength(1)
// Verify readdir works at the deep level
const files = await vfs.readdir('/level1/level2/level3')
expect(files).toContain('deep.txt')
// Update the file and verify relationship persists
await vfs.writeFile('/level1/level2/level3/deep.txt', 'Updated deep content')
const relationsAfterUpdate = await brain.related({
from: level3Id,
to: fileEntityId,
type: VerbType.Contains
})
expect(relationsAfterUpdate).toHaveLength(1)
// readdir should still work
const filesAfterUpdate = await vfs.readdir('/level1/level2/level3')
expect(filesAfterUpdate).toContain('deep.txt')
})
})