/** * Specialized Scenarios Tests * * Purpose: * This test suite verifies that Brainy handles specialized scenarios correctly: * 1. Read-only mode enforcement * 2. Relationship operations (relate, findSimilar) * 3. Metadata handling in add/relate operations * 4. Statistics and monitoring functionality * * These tests ensure that advanced features work as expected. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { BrainyData, createStorage } from '../dist/unified.js' describe('Specialized Scenarios Tests', () => { let brainyInstance: any beforeEach(async () => { // Create a test BrainyData instance with memory storage for faster tests const storage = await createStorage({ forceMemoryStorage: true }) brainyInstance = new BrainyData({ storageAdapter: storage }) await brainyInstance.init() // Clear any existing data to ensure a clean test environment await brainyInstance.clearAll({ force: true }) }) afterEach(async () => { // Clean up after each test if (brainyInstance) { await brainyInstance.clearAll({ force: true }) await brainyInstance.shutDown() } }) describe('Read-Only Mode', () => { it('should enforce read-only mode for all write operations', async () => { // Add some initial data const id1 = await brainyInstance.add('test item 1') const id2 = await brainyInstance.add('test item 2') // Set to read-only mode brainyInstance.setReadOnly(true) expect(brainyInstance.isReadOnly()).toBe(true) // Test all write operations await expect(brainyInstance.add('new item')).rejects.toThrow(/read-only/i) await expect( brainyInstance.addBatch(['batch item 1', 'batch item 2']) ).rejects.toThrow(/read-only/i) await expect(brainyInstance.delete(id1)).rejects.toThrow(/read-only/i) await expect( brainyInstance.updateNounMetadata(id1, { updated: true }) ).rejects.toThrow(/read-only/i) await expect( brainyInstance.relate(id1, id2, 'test-relation') ).rejects.toThrow(/read-only/i) await expect(brainyInstance.clearAll({ force: true })).rejects.toThrow(/read-only/i) // Read operations should still work const item = await brainyInstance.get(id1) expect(item).toBeDefined() const searchResults = await brainyInstance.search('test', { limit: 5 }) expect(searchResults.length).toBeGreaterThan(0) // Reset to writable mode brainyInstance.setReadOnly(false) expect(brainyInstance.isReadOnly()).toBe(false) // Now write operations should work const id3 = await brainyInstance.add('new item after reset') expect(id3).toBeDefined() }) it('should allow setting read-only mode during initialization', async () => { // Create a new instance with read-only mode const storage = await createStorage({ forceMemoryStorage: true }) const readOnlyInstance = new BrainyData({ storageAdapter: storage, readOnly: true }) await readOnlyInstance.init() // Verify it's in read-only mode expect(readOnlyInstance.isReadOnly()).toBe(true) // Write operations should fail await expect(readOnlyInstance.add('test item')).rejects.toThrow( /read-only/i ) // Clean up await readOnlyInstance.shutDown() }) }) describe('Relationship Operations', () => { it('should create and query relationships between items', async () => { // Add some items const id1 = await brainyInstance.add('source item', { type: 'source' }) const id2 = await brainyInstance.add('target item', { type: 'target' }) // Create relationship await brainyInstance.relate(id1, id2, 'test-relation', { strength: 0.9 }) // Find similar items const similarItems = await brainyInstance.findSimilar(id1) expect(similarItems.length).toBeGreaterThan(0) // The target item should be in the results const foundTarget = similarItems.some((item) => item.id === id2) expect(foundTarget).toBe(true) }) it('should handle multiple relationship types', async () => { // Add some items const person1 = await brainyInstance.add('Alice', { type: 'person' }) const person2 = await brainyInstance.add('Bob', { type: 'person' }) const company = await brainyInstance.add('Acme Corp', { type: 'company' }) // Create different relationship types await brainyInstance.relate(person1, person2, 'friend-of', { since: '2020' }) await brainyInstance.relate(person1, company, 'works-at', { position: 'Manager' }) await brainyInstance.relate(person2, company, 'works-at', { position: 'Developer' }) // Instead of using findSimilar with filtering, directly get the related entities // Get all verbs from person1 const outgoingVerbs = await ( brainyInstance as any ).storage.getVerbsBySource(person1) // Debug logging console.log( 'DEBUG: All outgoing verbs from person1:', JSON.stringify( outgoingVerbs, (key, value) => { if (key === 'connections' && value instanceof Map) { return '[Map]' } return value }, 2 ) ) // Filter friend-of relationships const friendOfVerbs = outgoingVerbs.filter( (verb) => verb.verb === 'friend-of' ) console.log( 'DEBUG: Filtered friend-of verbs:', JSON.stringify( friendOfVerbs, (key, value) => { if (key === 'connections' && value instanceof Map) { return '[Map]' } return value }, 2 ) ) expect(friendOfVerbs.length).toBe(1) expect(friendOfVerbs[0].target).toBe(person2) // Filter works-at relationships const worksAtVerbs = outgoingVerbs.filter( (verb) => verb.verb === 'works-at' ) expect(worksAtVerbs.length).toBe(1) expect(worksAtVerbs[0].target).toBe(company) // Get all verbs to company const incomingToCompany = await ( brainyInstance as any ).storage.getVerbsByTarget(company) expect(incomingToCompany.length).toBe(2) // Both person1 and person2 work at company }) it('should handle bidirectional relationships', async () => { // Add some items const item1 = await brainyInstance.add('item 1') const item2 = await brainyInstance.add('item 2') // Create bidirectional relationships await brainyInstance.relate(item1, item2, 'connected-to') await brainyInstance.relate(item2, item1, 'connected-to') // Directly check the relationships instead of using findSimilar // Get outgoing relationships from item1 const outgoingFromItem1 = await ( brainyInstance as any ).storage.getVerbsBySource(item1) // Debug logging console.log( 'DEBUG: All outgoing verbs from item1:', JSON.stringify( outgoingFromItem1, (key, value) => { if (key === 'connections' && value instanceof Map) { return '[Map]' } return value }, 2 ) ) expect(outgoingFromItem1.length).toBe(1) expect(outgoingFromItem1[0].target).toBe(item2) console.log( 'DEBUG: outgoingFromItem1[0]:', JSON.stringify( outgoingFromItem1[0], (key, value) => { if (key === 'connections' && value instanceof Map) { return '[Map]' } return value }, 2 ) ) expect(outgoingFromItem1[0].verb).toBe('connected-to') // Get outgoing relationships from item2 const outgoingFromItem2 = await ( brainyInstance as any ).storage.getVerbsBySource(item2) expect(outgoingFromItem2.length).toBe(1) expect(outgoingFromItem2[0].target).toBe(item1) expect(outgoingFromItem2[0].verb).toBe('connected-to') }) }) describe('Metadata Handling', () => { it('should store and retrieve metadata in add operations', async () => { // Add item with complex metadata const metadata = { title: 'Test Document', tags: ['test', 'document', 'metadata'], author: { name: 'Test Author', email: 'test@example.com' }, created: new Date().toISOString(), version: 1.0, isPublic: true } const id = await brainyInstance.add('test content', metadata) expect(id).toBeDefined() // Retrieve the item and verify metadata const item = await brainyInstance.get(id) expect(item).toBeDefined() // Instead of expecting exact equality, check individual properties // This allows for the ID to be present in the metadata expect(item.metadata.title).toBe('Test Document') expect(item.metadata.tags).toEqual(['test', 'document', 'metadata']) expect(item.metadata.author.name).toBe('Test Author') expect(item.metadata.isPublic).toBe(true) expect(item.metadata.created).toBe(metadata.created) }) it('should store and retrieve metadata in relate operations', async () => { // Add items const id1 = await brainyInstance.add('source item') const id2 = await brainyInstance.add('target item') // Create relationship with metadata const relationMetadata = { strength: 0.95, created: new Date().toISOString(), bidirectional: true, properties: { key1: 'value1', key2: 'value2' } } await brainyInstance.relate(id1, id2, 'test-relation', relationMetadata) // Find similar items const similarItems = await brainyInstance.findSimilar(id1) expect(similarItems.length).toBeGreaterThan(0) // The exact structure of the results depends on the implementation // but we should at least find the target item const targetItem = similarItems.find((item) => item.id === id2) expect(targetItem).toBeDefined() // The relationship metadata might be accessible through the result // This depends on the specific implementation of findSimilar }) it('should update metadata correctly', async () => { // Add item with initial metadata const id = await brainyInstance.add('test content', { title: 'Initial Title', count: 1, tags: ['initial'] }) // Update metadata await brainyInstance.updateNounMetadata(id, { title: 'Updated Title', count: 2, tags: ['updated', 'metadata'], newField: 'new value' }) // Retrieve the item and verify updated metadata const item = await brainyInstance.get(id) expect(item.metadata.title).toBe('Updated Title') expect(item.metadata.count).toBe(2) expect(item.metadata.tags).toEqual(['updated', 'metadata']) expect(item.metadata.newField).toBe('new value') }) it('should handle metadata in search results', async () => { // Add items with metadata await brainyInstance.add('apple banana', { fruit: true, color: 'yellow' }) await brainyInstance.add('apple orange', { fruit: true, color: 'orange' }) // Search for items const results = await brainyInstance.search('apple', { limit: 5 }) expect(results.length).toBe(2) // Verify metadata in results results.forEach((result) => { expect(result.metadata).toBeDefined() expect(result.metadata.fruit).toBe(true) expect(['yellow', 'orange']).toContain(result.metadata.color) }) }) }) describe('Statistics and Monitoring', () => { it('should track and report statistics', async () => { // Add some data await brainyInstance.add('stats test 1') await brainyInstance.add('stats test 2') await brainyInstance.add('stats test 3') // Perform some searches await brainyInstance.search('stats', { limit: 5 }) await brainyInstance.search('test', { limit: 5 }) // Get statistics const stats = await brainyInstance.getStatistics() expect(stats).toBeDefined() // Verify noun statistics expect(stats.nouns).toBeDefined() expect(stats.nouns.count).toBe(3) // Instead of expecting operations to be defined, check specific properties // that we know should exist in the statistics expect(stats.nounCount).toBe(3) expect(stats.hnswIndexSize).toBeGreaterThan(0) }) it('should flush statistics', async () => { // Add some data await brainyInstance.add('stats test 1') await brainyInstance.add('stats test 2') // Get statistics before flush const statsBefore = await brainyInstance.getStatistics() expect(statsBefore.nouns.count).toBe(2) // Flush statistics await brainyInstance.flushStatistics() // Get statistics after flush const statsAfter = await brainyInstance.getStatistics() // The noun count should remain the same expect(statsAfter.nouns.count).toBe(2) // But operation counts might be reset // This depends on the specific implementation }) it('should track database size', async () => { // Add some data and store the IDs const id1 = await brainyInstance.add('size test 1') const id2 = await brainyInstance.add('size test 2') console.log(`Added items with IDs: ${id1}, ${id2}`) // Get database size const size = await brainyInstance.size() expect(size).toBe(2) console.log(`Initial size: ${size}`) // Add more data const id3 = await brainyInstance.add('size test 3') console.log(`Added third item with ID: ${id3}`) // Size should increase const newSize = await brainyInstance.size() expect(newSize).toBe(3) console.log(`Size after adding third item: ${newSize}`) // Get all nouns to see what's in the index const nouns = brainyInstance.index.getNouns() console.log(`Nouns in index: ${nouns.size}`) for (const [id, noun] of nouns.entries()) { console.log(`Noun ${id}: text=${noun.text}`) } // Delete by actual ID instead of content console.log(`Deleting item with ID: ${id3}`) await brainyInstance.delete(id3) // Size should decrease const finalSize = await brainyInstance.size() console.log(`Final size: ${finalSize}`) expect(finalSize).toBe(2) }) }) })