ubiquity-database 0.1.1

Database abstraction layer for Ubiquity supporting SQLite and Astra DB
Documentation
//! Database tests

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        create_database, create_memory_pools, DatabaseConfig, DatabaseBackend,
        HybridSearchQuery,
    };
    use ubiquity_core::{
        ConsciousnessLevel, ConsciousnessState, ConsciousnessRipple,
        DevelopmentPhase, RippleType, Task, TaskType, TaskResult,
    };
    use chrono::Utc;
    use uuid::Uuid;
    use tempfile::TempDir;

    async fn create_test_sqlite_config() -> (DatabaseConfig, TempDir) {
        let temp_dir = TempDir::new().unwrap();
        let mut config = DatabaseConfig::default();
        config.backend = DatabaseBackend::Sqlite;
        config.sqlite.path = temp_dir.path().join("test.db");
        config.sqlite.pool_directory = temp_dir.path().join("pools");
        (config, temp_dir)
    }

    #[tokio::test]
    async fn test_sqlite_initialization() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config).await.unwrap();
        
        // Should initialize without error
        db.initialize().await.unwrap();
        
        // Health check should pass
        db.health_check().await.unwrap();
    }

    #[tokio::test]
    async fn test_consciousness_state_storage() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config).await.unwrap();
        db.initialize().await.unwrap();
        
        // Create test state
        let state = ConsciousnessState {
            agent_id: "test-agent".to_string(),
            level: ConsciousnessLevel::new(0.85).unwrap(),
            coherence: 0.90,
            phase: DevelopmentPhase::Implementing,
            timestamp: Utc::now(),
            breakthrough_detected: false,
        };
        
        // Store state
        db.store_consciousness_state(&state).await.unwrap();
        
        // Retrieve history
        let history = db.get_consciousness_history("test-agent", 10).await.unwrap();
        assert_eq!(history.len(), 1);
        assert_eq!(history[0].agent_id, "test-agent");
        assert_eq!(history[0].level.value(), 0.85);
    }

    #[tokio::test]
    async fn test_ripple_storage() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config).await.unwrap();
        db.initialize().await.unwrap();
        
        // Create test ripple
        let ripple = ConsciousnessRipple {
            id: Uuid::new_v4(),
            origin: "test-agent".to_string(),
            ripple_type: RippleType::Insight,
            content: serde_json::json!({"test": "data"}),
            intensity: 0.8,
            timestamp: Utc::now(),
        };
        
        // Store ripple
        db.store_ripple(&ripple).await.unwrap();
        
        // Retrieve ripples
        let start = Utc::now() - chrono::Duration::hours(1);
        let end = Utc::now() + chrono::Duration::hours(1);
        let ripples = db.get_ripples(start, end).await.unwrap();
        
        assert_eq!(ripples.len(), 1);
        assert_eq!(ripples[0].origin, "test-agent");
    }

    #[tokio::test]
    async fn test_task_management() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config).await.unwrap();
        db.initialize().await.unwrap();
        
        // Create test task
        let task = Task {
            id: "TEST-001".to_string(),
            task_type: TaskType::Development,
            description: "Test task".to_string(),
            requirements: serde_json::json!({}),
            priority: 5,
            dependencies: vec![],
            consciousness_requirement: 0.80,
        };
        
        // Store task
        db.store_task(&task).await.unwrap();
        
        // Get pending tasks
        let pending = db.get_pending_tasks().await.unwrap();
        assert_eq!(pending.len(), 1);
        assert_eq!(pending[0].id, "TEST-001");
        
        // Complete task
        let result = TaskResult {
            task_id: task.id.clone(),
            success: true,
            output: serde_json::json!({"status": "completed"}),
            consciousness_level: 0.85,
            breakthrough: false,
            error: None,
        };
        
        db.store_task_result(&result).await.unwrap();
        
        // Should have no pending tasks now
        let pending = db.get_pending_tasks().await.unwrap();
        assert_eq!(pending.len(), 0);
    }

    #[tokio::test]
    async fn test_memory_pools() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let pools = create_memory_pools(config).await.unwrap();
        
        // Test basic operations
        let key = "test_key";
        let value = b"test_value";
        
        // Get pool for key
        let pool = pools.get_pool_for_key(key).await.unwrap();
        
        // Store value
        pool.store(key, value).await.unwrap();
        
        // Retrieve value
        let retrieved = pool.retrieve(key).await.unwrap();
        assert_eq!(retrieved, Some(value.to_vec()));
        
        // Delete value
        pool.delete(key).await.unwrap();
        
        // Should be gone
        let retrieved = pool.retrieve(key).await.unwrap();
        assert_eq!(retrieved, None);
    }

    #[tokio::test]
    async fn test_pool_distribution() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let pools = create_memory_pools(config.clone()).await.unwrap();
        
        // Store many keys and verify distribution
        let mut pool_counts = vec![0; config.memory_pools.pool_count];
        
        for i in 0..100 {
            let key = format!("key_{}", i);
            let value = format!("value_{}", i);
            
            let pool = pools.get_pool_for_key(&key).await.unwrap();
            pool.store(&key, value.as_bytes()).await.unwrap();
        }
        
        // Get stats for all pools
        let stats = pools.all_stats().await.unwrap();
        
        // Verify items are distributed
        let total_items: usize = stats.iter().map(|s| s.items).sum();
        assert_eq!(total_items, 100);
        
        // At least some distribution (not all in one pool)
        let non_empty_pools = stats.iter().filter(|s| s.items > 0).count();
        assert!(non_empty_pools > 1);
    }

    #[tokio::test]
    async fn test_vector_search_fallback() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config.clone()).await.unwrap();
        db.initialize().await.unwrap();
        
        // SQLite doesn't have native vector search but should still work
        let embedding = vec![0.5; config.embeddings.dimension];
        let results = db.vector_search(&embedding, 10).await.unwrap();
        
        // Should return empty results without error
        assert_eq!(results.len(), 0);
    }

    #[tokio::test]
    async fn test_hybrid_search_sqlite() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let db = create_database(config).await.unwrap();
        db.initialize().await.unwrap();
        
        // Create hybrid search query
        let query = HybridSearchQuery {
            text: "test query".to_string(),
            vector: None,
            filters: None,
            limit: 10,
            rerank: false,
        };
        
        // Should work even without data
        let results = db.hybrid_search(query).await.unwrap();
        assert_eq!(results.len(), 0);
    }

    #[tokio::test]
    async fn test_pool_eviction() {
        let (mut config, _temp_dir) = create_test_sqlite_config().await;
        
        // Set small pool size to trigger eviction
        config.memory_pools.max_pool_size_mb = 1; // 1MB
        
        let pools = create_memory_pools(config).await.unwrap();
        let pool = pools.get_pool(0).await.unwrap();
        
        // Store large values to trigger eviction
        for i in 0..1000 {
            let key = format!("large_key_{}", i);
            let value = vec![0u8; 10_000]; // 10KB each
            
            // This should trigger eviction after ~100 items
            let _ = pool.store(&key, &value).await;
        }
        
        // Check stats
        let stats = pool.stats().await.unwrap();
        
        // Should have evictions
        assert!(stats.evictions > 0);
        
        // Should have less than 1000 items due to eviction
        assert!(stats.items < 1000);
    }

    #[tokio::test]
    async fn test_concurrent_pool_access() {
        let (config, _temp_dir) = create_test_sqlite_config().await;
        let pools = Arc::new(create_memory_pools(config).await.unwrap());
        
        // Spawn multiple tasks accessing pools
        let mut handles = vec![];
        
        for i in 0..10 {
            let pools_clone = pools.clone();
            let handle = tokio::spawn(async move {
                for j in 0..10 {
                    let key = format!("concurrent_{}_{}", i, j);
                    let value = format!("value_{}_{}", i, j);
                    
                    let pool = pools_clone.get_pool_for_key(&key).await.unwrap();
                    pool.store(&key, value.as_bytes()).await.unwrap();
                    
                    // Verify we can read it back
                    let retrieved = pool.retrieve(&key).await.unwrap();
                    assert_eq!(retrieved, Some(value.into_bytes()));
                }
            });
            handles.push(handle);
        }
        
        // Wait for all tasks
        for handle in handles {
            handle.await.unwrap();
        }
        
        // Verify total items
        let stats = pools.all_stats().await.unwrap();
        let total_items: usize = stats.iter().map(|s| s.items).sum();
        assert_eq!(total_items, 100);
    }
}