/** * Core Functionality Tests * Tests core Brainy features as a consumer would use them */ import { describe, it, expect, beforeAll } from 'vitest' /** * Helper function to create a 512-dimensional vector for testing * @param primaryIndex The index to set to 1.0, all other indices will be 0.0 * @returns A 512-dimensional vector with a single 1.0 value at the specified index */ function createTestVector(primaryIndex: number = 0): number[] { const vector = new Array(512).fill(0) vector[primaryIndex % 512] = 1.0 return vector } describe('Brainy Core Functionality', () => { let brainy: any beforeAll(async () => { // Load brainy library as a consumer would brainy = await import('../dist/unified.js') }) describe('Library Exports', () => { it('should export BrainyData class', () => { expect(brainy.BrainyData).toBeDefined() expect(typeof brainy.BrainyData).toBe('function') }) it('should export environment detection functions', () => { expect(typeof brainy.isBrowser).toBe('function') expect(typeof brainy.isNode).toBe('function') expect(typeof brainy.isWebWorker).toBe('function') expect(typeof brainy.areWebWorkersAvailable).toBe('function') expect(typeof brainy.isThreadingAvailable).toBe('function') }) it('should export embedding function creator', () => { expect(typeof brainy.createEmbeddingFunction).toBe('function') }) it('should export environment object', () => { expect(brainy.environment).toBeDefined() expect(typeof brainy.environment).toBe('object') expect(brainy.environment).toHaveProperty('isBrowser') expect(brainy.environment).toHaveProperty('isNode') expect(brainy.environment).toHaveProperty('isServerless') }) }) describe('BrainyData Configuration', () => { it('should create instance with minimal configuration', () => { const data = new brainy.BrainyData({}) expect(data).toBeDefined() expect(data.dimensions).toBe(512) }) it('should create instance with full configuration', () => { const data = new brainy.BrainyData({ metric: 'cosine', maxConnections: 32, efConstruction: 200, storage: 'memory' }) expect(data).toBeDefined() expect(data.dimensions).toBe(512) }) it('should not throw with valid configuration parameters', () => { // Dimensions are now fixed at 512 and not configurable expect(() => { new brainy.BrainyData({ metric: 'cosine' }) }).not.toThrow() expect(() => { new brainy.BrainyData({ metric: 'euclidean' }) }).not.toThrow() }) it('should use default values for optional parameters', () => { const data = new brainy.BrainyData({}) expect(data.dimensions).toBe(512) // Should have reasonable defaults for other parameters expect(data.maxConnections).toBeGreaterThan(0) expect(data.efConstruction).toBeGreaterThan(0) }) }) describe('Vector Operations', () => { it('should handle vector addition and search', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() await data.clear() // Clear any existing data // Add vectors using helper function await data.add(createTestVector(0), { id: 'v1', label: 'x-axis' }) await data.add(createTestVector(1), { id: 'v2', label: 'y-axis' }) await data.add(createTestVector(2), { id: 'v3', label: 'z-axis' }) // Search for similar vector const results = await data.search(createTestVector(0), 1) expect(results).toBeDefined() expect(results.length).toBe(1) expect(results[0].metadata.id).toBe('v1') }) it('should handle batch vector operations', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() await data.clear() // Clear any existing data // Add multiple vectors const vectors = [ { vector: createTestVector(10), metadata: { id: 'batch1' } }, { vector: createTestVector(20), metadata: { id: 'batch2' } }, { vector: createTestVector(30), metadata: { id: 'batch3' } } ] for (const { vector, metadata } of vectors) { await data.add(vector, metadata) } // Search should return results const results = await data.search(createTestVector(15), 3) expect(results.length).toBe(3) }) it('should handle different distance metrics', async () => { const euclideanData = new brainy.BrainyData({ metric: 'euclidean' }) const cosineData = new brainy.BrainyData({ metric: 'cosine' }) await euclideanData.init() await cosineData.init() // Clear any existing data to ensure test isolation await euclideanData.clear() await cosineData.clear() const vector = createTestVector(5) const metadata = { id: 'test' } await euclideanData.add(vector, metadata) await cosineData.add(vector, metadata) const euclideanResults = await euclideanData.search(vector, 1) const cosineResults = await cosineData.search(vector, 1) expect(euclideanResults.length).toBe(1) expect(cosineResults.length).toBe(1) // Both should find the exact match, but distances might differ expect(euclideanResults[0].metadata.id).toBe('test') expect(cosineResults[0].metadata.id).toBe('test') }) }) describe('Text Processing', () => { it( 'should handle text items with embedding function', async () => { const embeddingFunction = brainy.createEmbeddingFunction() const data = new brainy.BrainyData({ embeddingFunction, dimensions: 512, // Universal Sentence Encoder produces 512-dimensional vectors metric: 'cosine', storage: { forceMemoryStorage: true } }) await data.init() // Add text items await data.addItem('Hello world', { id: 'greeting', type: 'text' }) await data.addItem('Goodbye world', { id: 'farewell', type: 'text' }) // Search with text const results = await data.search('Hi there', 1) expect(results).toBeDefined() expect(results.length).toBeGreaterThan(0) expect(results[0].metadata).toHaveProperty('id') }, globalThis.testUtils?.timeout || 30000 ) it( 'should handle mixed vector and text operations', async () => { const embeddingFunction = brainy.createEmbeddingFunction() const data = new brainy.BrainyData({ embeddingFunction, dimensions: 512, // Universal Sentence Encoder produces 512-dimensional vectors metric: 'cosine' }) await data.init() // Add text item await data.addItem('Machine learning', { id: 'text1', type: 'text' }) // Add vector item (using embedding of similar text) const embedding = await embeddingFunction('Artificial intelligence') await data.add(embedding, { id: 'vector1', type: 'vector' }) // Search should find both const results = await data.search('AI and ML', 2) expect(results).toBeDefined() expect(results.length).toBeGreaterThan(0) }, globalThis.testUtils?.timeout || 30000 ) }) describe('Error Handling', () => { it('should handle invalid vector dimensions', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() // Try to add vector with wrong dimensions await expect(data.add([1, 2], { id: 'wrong' })).rejects.toThrow() await expect(data.add(new Array(100).fill(0), { id: 'wrong' })).rejects.toThrow() }) it('should handle search before initialization', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) // Try to search without initialization await expect(data.search(createTestVector(0), 1)).rejects.toThrow() }) it('should handle empty search results gracefully', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() await data.clear() // Clear any existing data // Search in empty database const results = await data.search(createTestVector(0), 1) expect(results).toBeDefined() expect(Array.isArray(results)).toBe(true) expect(results.length).toBe(0) }) }) describe('Performance and Scalability', () => { it('should handle moderate number of vectors efficiently', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() const startTime = Date.now() // Add 100 test vectors for (let i = 0; i < 100; i++) { await data.add(createTestVector(i), { id: `item_${i}`, index: i }) } const addTime = Date.now() - startTime // Search should be fast const searchStart = Date.now() const results = await data.search(createTestVector(50), 10) const searchTime = Date.now() - searchStart expect(results.length).toBeLessThanOrEqual(10) expect(addTime).toBeLessThan(10000) // Should complete within 10 seconds expect(searchTime).toBeLessThan(1000) // Search should be under 1 second }) it('should maintain search quality with more data', async () => { // Create database with proper configuration for testing const db = new brainy.BrainyData({ embeddingFunction: brainy.createEmbeddingFunction(), metric: 'cosine' }) await db.init() await db.clear() // Clear any existing data // Add known data await db.add('known data', { id: 'known' }) // Add noise data for (let i = 0; i < 100; i++) { await db.add(`noise_${i}`, { id: `noise_${i}` }) } // Perform search using the correct method const results = await db.search('known data', 10) // Debugging output console.log( 'Search results:', results.map((r) => r.metadata?.id) ) // Assertions expect(results.length).toBeGreaterThan(0) expect(results[0].metadata?.id).toBe('known') }) }) describe('Database Statistics', () => { it('should provide accurate statistics about the database', async () => { const data = new brainy.BrainyData({ metric: 'euclidean' }) await data.init() await data.clear() // Clear any existing data // Add some vectors (nouns) await data.add(createTestVector(0), { id: 'v1', label: 'x-axis' }) await data.add(createTestVector(1), { id: 'v2', label: 'y-axis' }) await data.add(createTestVector(2), { id: 'v3', label: 'z-axis' }) // Add some connections (verbs) await data.connect('v1', 'v2', 'related_to') await data.connect('v2', 'v3', 'related_to') // Get statistics const stats = await data.getStatistics() // Debug: Log all nouns in the database const allNouns = await data.getAllNouns() console.log('All nouns in database:', allNouns.map(n => n.id)) // Debug: Log all verbs in the database const allVerbs = await data.getAllVerbs() console.log('All verbs in database:', allVerbs.map(v => v.id)) // Debug: Log the verb IDs set used in getStatistics const verbIds = new Set(allVerbs.map(verb => verb.id)) console.log('Verb IDs set:', Array.from(verbIds)) // Verify statistics expect(stats).toBeDefined() expect(stats).toHaveProperty('nounCount') expect(stats).toHaveProperty('verbCount') expect(stats).toHaveProperty('metadataCount') expect(stats).toHaveProperty('hnswIndexSize') // Verify counts expect(stats.nounCount).toBe(3) expect(stats.verbCount).toBe(2) expect(stats.hnswIndexSize).toBe(3) }) }) })