zipora 3.1.7

High-performance Rust implementation providing advanced data structures and compression algorithms with memory safety guarantees. Features LRU page cache, sophisticated caching layer, fiber-based concurrency, real-time compression, secure memory pools, SIMD optimizations, and complete C FFI for migration from C++.
use super::*;
use crate::succinct::RankSelectInterleaved256;


    #[test]
    fn test_unified_trie_creation() {
        let trie: ZiporaTrie = ZiporaTrie::new();
        assert_eq!(trie.len(), 0);
        assert!(trie.is_empty());
    }

    #[test]
    fn test_cache_optimized_config() {
        let trie: ZiporaTrie = ZiporaTrie::with_config(ZiporaTrieConfig::cache_optimized());
        assert!(trie.is_cache_optimized());
    }

    #[test]
    fn test_space_optimized_config() {
        let trie: ZiporaTrie = ZiporaTrie::with_config(ZiporaTrieConfig::space_optimized());
        assert_eq!(trie.len(), 0);
    }

    #[test]
    fn test_string_specialized_config() {
        let trie: ZiporaTrie = ZiporaTrie::with_config(ZiporaTrieConfig::string_specialized());
        assert_eq!(trie.len(), 0);
    }

    #[test]
    fn test_double_array_insert_contains() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        // Default is now DoubleArray
        trie.insert(b"hello").unwrap();
        assert_eq!(trie.len(), 1);
        assert!(trie.contains(b"hello"));
        assert!(!trie.contains(b"world"));

        trie.insert(b"world").unwrap();
        assert_eq!(trie.len(), 2);
        assert!(trie.contains(b"hello"));
        assert!(trie.contains(b"world"));

        trie.insert(b"help").unwrap();
        assert_eq!(trie.len(), 3);
        assert!(trie.contains(b"help"));
        assert!(trie.contains(b"hello"));

        // Duplicate insert should not increase len
        trie.insert(b"hello").unwrap();
        assert_eq!(trie.len(), 3);
    }

    #[test]
    fn test_double_array_keys() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"apple").unwrap();
        trie.insert(b"app").unwrap();
        trie.insert(b"banana").unwrap();

        let mut keys = trie.keys();
        keys.sort();
        assert_eq!(keys.len(), 3);
        assert_eq!(keys[0], b"app");
        assert_eq!(keys[1], b"apple");
        assert_eq!(keys[2], b"banana");
    }

    #[test]
    fn test_double_array_prefix_with_empty_key() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"").unwrap();
        trie.insert(b"a").unwrap();
        trie.insert(b"ab").unwrap();
        trie.insert(b"abc").unwrap();
        trie.insert(b"abd").unwrap();
        trie.insert(b"b").unwrap();

        let all = trie.keys_with_prefix(b"");
        assert_eq!(
            all.len(),
            6,
            "keys_with_prefix('') should return all 6 keys"
        );
    }

    #[test]
    fn test_double_array_empty_key() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"").unwrap();
        trie.insert(b"a").unwrap();
        trie.insert(b"ab").unwrap();

        assert_eq!(trie.len(), 3);
        assert!(trie.contains(b""));
        assert!(trie.contains(b"a"));
        assert!(trie.contains(b"ab"));

        let mut keys = trie.keys();
        keys.sort();
        assert_eq!(keys.len(), 3, "Should have 3 keys including empty");
        assert_eq!(keys[0], b"");
        assert_eq!(keys[1], b"a");
        assert_eq!(keys[2], b"ab");
    }

    // --- Coverage tests for each improvement ---

    /// Issue #1: Lazy stats — verify stats() works correctly after inserts
    #[test]
    fn test_lazy_stats() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        for i in 0..100 {
            trie.insert(format!("key{:03}", i).as_bytes()).unwrap();
        }
        assert_eq!(trie.len(), 100);
        let stats = trie.stats();
        assert_eq!(stats.num_keys, 100);
        assert!(stats.memory_usage > 0);
        assert!(stats.num_states > 0);
    }

    /// Issue #2: No double traversal — duplicate insert does not increase len
    #[test]
    fn test_no_double_traversal_duplicate() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"abc").unwrap();
        trie.insert(b"abc").unwrap();
        trie.insert(b"abc").unwrap();
        assert_eq!(trie.len(), 1);

        trie.insert(b"def").unwrap();
        trie.insert(b"def").unwrap();
        assert_eq!(trie.len(), 2);
    }

    /// Issue #3: Compact PatriciaNode — Patricia still works with compact children
    #[test]
    fn test_patricia_compact_node() {
        let config = ZiporaTrieConfig {
            trie_strategy: crate::fsa::TrieStrategy::Patricia {
                max_path_length: 64,
                compression_threshold: 4,
                adaptive_compression: true,
            },
            ..ZiporaTrieConfig::default()
        };
        let mut trie: ZiporaTrie = ZiporaTrie::with_config(config);
        trie.insert(b"hello").unwrap();
        trie.insert(b"help").unwrap();
        trie.insert(b"world").unwrap();

        assert_eq!(trie.len(), 3);
        assert!(trie.contains(b"hello"));
        assert!(trie.contains(b"help"));
        assert!(trie.contains(b"world"));
        assert!(!trie.contains(b"hel"));
    }

    /// Issue #4/#5: find_free_base + relocate — many inserts don't panic
    #[test]
    fn test_find_free_base_many_inserts() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        // Insert many keys to stress find_free_base and relocation
        for i in 0..500 {
            trie.insert(format!("key_{:04}", i).as_bytes()).unwrap();
        }
        assert_eq!(trie.len(), 500);
        // Verify random lookups
        assert!(trie.contains(b"key_0000"));
        assert!(trie.contains(b"key_0250"));
        assert!(trie.contains(b"key_0499"));
        assert!(!trie.contains(b"key_0500"));
    }

    /// Issue #6: Amortized growth — large insert doesn't OOM or take forever
    #[test]
    fn test_amortized_growth() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        // 1000 inserts should complete quickly with 1.5x growth
        for i in 0..1000 {
            trie.insert(format!("{:04}", i).as_bytes()).unwrap();
        }
        assert_eq!(trie.len(), 1000);
    }

    /// Issue #8: TrieMap — key-value storage
    #[test]
    fn test_trie_map() {
        let mut map = ZiporaTrieMap::<u32, RankSelectInterleaved256>::new();
        map.insert(b"hello", 42).unwrap();
        map.insert(b"world", 100).unwrap();
        map.insert(b"help", 7).unwrap();

        assert_eq!(map.get(b"hello"), Some(42));
        assert_eq!(map.get(b"world"), Some(100));
        assert_eq!(map.get(b"help"), Some(7));
        assert_eq!(map.get(b"missing"), None);
        assert_eq!(map.len(), 3);

        // Update existing key
        let prev = map.insert(b"hello", 99).unwrap();
        assert_eq!(prev, Some(42));
        assert_eq!(map.get(b"hello"), Some(99));
        assert_eq!(map.len(), 3); // len unchanged
    }

    /// Issue #9: Bulk construction
    #[test]
    fn test_build_from_sorted() {
        let keys: Vec<&[u8]> = vec![b"apple", b"application", b"apply", b"banana", b"band"];
        let trie: ZiporaTrie =
            ZiporaTrie::build_from_sorted(&keys, ZiporaTrieConfig::default()).unwrap();

        assert_eq!(trie.len(), 5);
        assert!(trie.contains(b"apple"));
        assert!(trie.contains(b"application"));
        assert!(trie.contains(b"apply"));
        assert!(trie.contains(b"banana"));
        assert!(trie.contains(b"band"));
        assert!(!trie.contains(b"ban"));
    }

    /// Issue #10: Default is DoubleArray
    #[test]
    fn test_default_is_double_array() {
        let config = ZiporaTrieConfig::default();
        assert!(matches!(
            config.trie_strategy,
            crate::fsa::TrieStrategy::DoubleArray { .. }
        ));
    }

    /// DoubleArray remove support
    #[test]
    fn test_double_array_remove() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"hello").unwrap();
        trie.insert(b"world").unwrap();
        assert_eq!(trie.len(), 2);

        assert!(trie.remove(b"hello").unwrap());
        assert_eq!(trie.len(), 1);
        assert!(!trie.contains(b"hello"));
        assert!(trie.contains(b"world"));

        // Remove non-existent key
        assert!(!trie.remove(b"missing").unwrap());
        assert_eq!(trie.len(), 1);
    }

    /// DoubleArray lookup_node_id + restore_string roundtrip
    #[test]
    fn test_double_array_node_id_roundtrip() {
        let mut trie: ZiporaTrie = ZiporaTrie::new();
        trie.insert(b"hello").unwrap();
        trie.insert(b"world").unwrap();

        let node_id = trie.lookup_node_id(b"hello").expect("should find hello");
        let restored = trie.restore_string(node_id).expect("should restore");
        assert_eq!(restored, b"hello");

        let node_id2 = trie.lookup_node_id(b"world").expect("should find world");
        let restored2 = trie.restore_string(node_id2).expect("should restore");
        assert_eq!(restored2, b"world");

        assert!(trie.lookup_node_id(b"missing").is_none());
    }