amaters-core 0.2.2

Core kernel for AmateRS - Fully Homomorphic Encrypted Database
Documentation
//! Chaos engineering tests for storage resilience.
//!
//! These tests inject failures or extreme conditions to verify the storage
//! layer handles them gracefully — no panics, clear error returns.

#[cfg(test)]
mod tests {
    use crate::profiling::ProfilingGuard;
    use crate::storage::EncryptedIndex;

    /// Attempt to open a storage engine at a path that cannot be created.
    /// Expect an error, not a panic.
    #[test]
    fn test_storage_handles_nonexistent_path() {
        use crate::storage::LsmTreeStorage;
        use crate::storage::{
            BlockCacheConfig, CompactionConfig, CompactionStrategy, LsmTreeConfig, MemtableConfig,
            SSTableConfig,
        };

        // Pick a path inside a non-existent deep directory that we will never create
        let bad_dir = std::env::temp_dir()
            .join("chaos_nonexistent_XYZ987")
            .join("deep")
            .join("nested");

        // Ensure it really doesn't exist
        let _ = std::fs::remove_dir_all(&bad_dir);

        let config = LsmTreeConfig {
            data_dir: bad_dir.join("data"),
            wal_dir: bad_dir.join("wal"),
            memtable_config: MemtableConfig {
                max_size_bytes: 4 * 1024 * 1024,
                enable_wal: false,
            },
            sstable_config: SSTableConfig {
                block_size: 4096,
                compression_type: crate::storage::CompressionType::None,
            },
            block_cache_config: BlockCacheConfig {
                max_size_bytes: 1024 * 1024,
                enable_stats: false,
            },
            compaction_config: CompactionConfig {
                strategy: CompactionStrategy::LevelBased,
                l0_threshold: 4,
                level_multiplier: 10,
                base_level_size: 10 * 1024 * 1024,
                max_compaction_bytes: 100 * 1024 * 1024,
                ..Default::default()
            },
            value_log_config: None,
            max_levels: 7,
            l0_compaction_threshold: 4,
            level_size_multiplier: 10,
        };

        // The LsmTreeStorage::with_config call should succeed (it creates the dirs),
        // but we verify that even if the base_dir truly can't be made, we get an error.
        // Force failure by making the path unwritable on Unix.
        // Actually, creating deep dirs should be fine normally; let's verify this succeeds
        // (documenting that LsmTree DOES handle dir creation itself).
        // The important chaos property: no panic from any path.
        let result = LsmTreeStorage::with_config(config);
        // Whether Ok or Err, we must not have panicked.
        let _ = result; // Accept both outcomes; the test passes as long as no panic occurred.

        // Cleanup
        let _ = std::fs::remove_dir_all(std::env::temp_dir().join("chaos_nonexistent_XYZ987"));
    }

    /// Verify that `EncryptedIndex` handles very large key sizes without panicking.
    #[test]
    fn test_index_handles_large_key() {
        let index = EncryptedIndex::new("chaos", "col", "f");
        let large_key = vec![0u8; 65536]; // 64 KiB key
        index.insert(&large_key, 1);
        let results = index.lookup_candidates(&large_key);
        assert_eq!(results, vec![1]);
    }

    /// Verify that `EncryptedIndex` survives 100K rapid operations without corruption.
    #[test]
    fn test_index_high_volume_operations() {
        let index = EncryptedIndex::new("chaos_vol", "col", "f");
        const N: u64 = 100_000;

        for i in 0..N {
            let key = format!("key{}", i % 1000); // 1000 unique keys
            index.insert(key.as_bytes(), i);
        }

        // Each key gets N/1000 = 100 entries (indices 0, 1000, 2000, ..., 99000)
        let mut entries = index.lookup_candidates(b"key0");
        entries.sort_unstable();
        assert_eq!(
            entries.len(),
            100,
            "key0 should have exactly 100 entries, got {}",
            entries.len()
        );
        // Spot-check the values
        assert_eq!(entries[0], 0);
        assert_eq!(entries[1], 1000);
        assert_eq!(entries[99], 99000);
    }

    /// Simulate corrupted serialized data → deserialize must return an error, not panic.
    #[test]
    fn test_index_deserialize_corrupted_data() {
        let corrupted = b"this is not valid oxicode data \x00\xff\xfe\xfd";
        let result = EncryptedIndex::deserialize(corrupted);
        assert!(
            result.is_err(),
            "Deserializing corrupted bytes must return Err, not panic"
        );
    }

    /// Verify `ProfilingGuard` works correctly under concurrent usage (no data races).
    #[test]
    fn test_profiling_guard_concurrent() {
        use std::sync::Arc;
        use std::sync::atomic::{AtomicU64, Ordering};
        use std::thread;

        let counter = Arc::new(AtomicU64::new(0));
        let handles: Vec<_> = (0..16)
            .map(|_| {
                let c = Arc::clone(&counter);
                thread::spawn(move || {
                    for _ in 0..1000 {
                        let _guard = ProfilingGuard::new("chaos_bench");
                        c.fetch_add(1, Ordering::Relaxed);
                    }
                })
            })
            .collect();

        for h in handles {
            h.join().expect("thread panicked");
        }

        assert_eq!(counter.load(Ordering::Relaxed), 16_000);
    }

    /// Verify that an empty EncryptedIndex returns empty on lookup.
    #[test]
    fn test_index_lookup_on_empty() {
        let index = EncryptedIndex::new("empty_chaos", "col", "f");
        let results = index.lookup_candidates(b"nonexistent");
        assert!(results.is_empty());
        assert_eq!(index.len(), 0);
        assert!(index.is_empty());
    }

    /// Verify remove on a key that was never inserted is a no-op (no panic).
    #[test]
    fn test_index_remove_nonexistent_key() {
        let index = EncryptedIndex::new("remove_chaos", "col", "f");
        // Should not panic
        index.remove(b"ghost_key", 42);
        assert!(index.is_empty());
    }
}