Skip to main content

noxu_engine/
verify.rs

1//! Environment verification utilities.
2//!
3//! Related verification functionality.
4
5use noxu_dbi::DatabaseImpl;
6use noxu_tree::NodeRwLock as RwLock;
7use noxu_tree::Tree;
8use noxu_tree::tree::{BinStub, InNodeStub, TreeNode};
9use noxu_util::NULL_LSN;
10use std::fmt;
11use std::sync::Arc;
12
13/// Result of an environment verification.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct VerifyResult {
16    /// Errors found during verification.
17    pub errors: Vec<VerifyError>,
18    /// Non-fatal warnings.
19    pub warnings: Vec<String>,
20    /// Number of databases verified.
21    pub databases_verified: u32,
22    /// Number of records verified.
23    pub records_verified: u64,
24    /// Whether the verification passed (no errors).
25    pub passed: bool,
26}
27
28impl VerifyResult {
29    /// Create a new passing result with no errors or warnings.
30    pub fn new() -> Self {
31        Self {
32            errors: Vec::new(),
33            warnings: Vec::new(),
34            databases_verified: 0,
35            records_verified: 0,
36            passed: true,
37        }
38    }
39
40    /// Create a result with errors.
41    pub fn with_errors(errors: Vec<VerifyError>) -> Self {
42        Self {
43            passed: errors.is_empty(),
44            errors,
45            warnings: Vec::new(),
46            databases_verified: 0,
47            records_verified: 0,
48        }
49    }
50
51    /// Add an error to the result.
52    pub fn add_error(&mut self, error: VerifyError) {
53        self.errors.push(error);
54        self.passed = false;
55    }
56
57    /// Add a warning to the result.
58    pub fn add_warning(&mut self, warning: String) {
59        self.warnings.push(warning);
60    }
61
62    /// Check if the verification passed.
63    pub fn is_passed(&self) -> bool {
64        self.passed
65    }
66
67    /// Get the number of errors.
68    pub fn error_count(&self) -> usize {
69        self.errors.len()
70    }
71
72    /// Get the number of warnings.
73    pub fn warning_count(&self) -> usize {
74        self.warnings.len()
75    }
76}
77
78impl Default for VerifyResult {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl fmt::Display for VerifyResult {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        writeln!(f, "Verification Result")?;
87        writeln!(f, "===================")?;
88        writeln!(
89            f,
90            "Status: {}",
91            if self.passed { "PASSED" } else { "FAILED" }
92        )?;
93        writeln!(f, "Databases verified: {}", self.databases_verified)?;
94        writeln!(f, "Records verified: {}", self.records_verified)?;
95        writeln!(f)?;
96
97        if !self.errors.is_empty() {
98            writeln!(f, "Errors ({}):", self.errors.len())?;
99            for error in &self.errors {
100                writeln!(f, "  - {}", error)?;
101            }
102            writeln!(f)?;
103        }
104
105        if !self.warnings.is_empty() {
106            writeln!(f, "Warnings ({}):", self.warnings.len())?;
107            for warning in &self.warnings {
108                writeln!(f, "  - {}", warning)?;
109            }
110            writeln!(f)?;
111        }
112
113        if self.errors.is_empty() && self.warnings.is_empty() {
114            writeln!(f, "No errors or warnings found.")?;
115        }
116
117        Ok(())
118    }
119}
120
121/// Types of verification errors.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum VerifyError {
124    /// B-tree structure error.
125    BtreeError { db_name: String, description: String },
126    /// Log file error.
127    LogError { file_number: u32, description: String },
128    /// Data inconsistency.
129    DataInconsistency { description: String },
130    /// Checksum mismatch.
131    ChecksumError { location: String, description: String },
132    /// Invalid node reference.
133    InvalidNodeReference { node_id: u64, description: String },
134    /// Database metadata error.
135    MetadataError { db_name: String, description: String },
136}
137
138impl fmt::Display for VerifyError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            VerifyError::BtreeError { db_name, description } => {
142                write!(f, "B-tree error in '{}': {}", db_name, description)
143            }
144            VerifyError::LogError { file_number, description } => {
145                write!(
146                    f,
147                    "Log file {:08x}.ndb error: {}",
148                    file_number, description
149                )
150            }
151            VerifyError::DataInconsistency { description } => {
152                write!(f, "Data inconsistency: {}", description)
153            }
154            VerifyError::ChecksumError { location, description } => {
155                write!(f, "Checksum error at {}: {}", location, description)
156            }
157            VerifyError::InvalidNodeReference { node_id, description } => {
158                write!(
159                    f,
160                    "Invalid node reference (ID {}): {}",
161                    node_id, description
162                )
163            }
164            VerifyError::MetadataError { db_name, description } => {
165                write!(f, "Metadata error in '{}': {}", db_name, description)
166            }
167        }
168    }
169}
170
171/// Configuration for verification.
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct VerifyConfig {
174    /// Whether to verify the B-tree structure.
175    pub verify_btree: bool,
176    /// Whether to verify log files.
177    pub verify_log: bool,
178    /// Whether to verify data checksums.
179    pub verify_data_checksums: bool,
180    /// Whether to repair problems found.
181    pub repair: bool,
182    /// Maximum number of errors before stopping.
183    pub max_errors: u32,
184    /// Whether to print verbose progress information.
185    pub verbose: bool,
186    /// Whether to verify only a specific database.
187    pub database_name: Option<String>,
188}
189
190impl VerifyConfig {
191    /// Create a new verification config with default settings.
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Enable B-tree verification.
197    pub fn with_btree_verification(mut self, enabled: bool) -> Self {
198        self.verify_btree = enabled;
199        self
200    }
201
202    /// Enable log file verification.
203    pub fn with_log_verification(mut self, enabled: bool) -> Self {
204        self.verify_log = enabled;
205        self
206    }
207
208    /// Enable data checksum verification.
209    pub fn with_checksum_verification(mut self, enabled: bool) -> Self {
210        self.verify_data_checksums = enabled;
211        self
212    }
213
214    /// Enable repair mode.
215    pub fn with_repair(mut self, enabled: bool) -> Self {
216        self.repair = enabled;
217        self
218    }
219
220    /// Set maximum number of errors.
221    pub fn with_max_errors(mut self, max: u32) -> Self {
222        self.max_errors = max;
223        self
224    }
225
226    /// Enable verbose output.
227    pub fn with_verbose(mut self, enabled: bool) -> Self {
228        self.verbose = enabled;
229        self
230    }
231
232    /// Verify only a specific database.
233    pub fn for_database(mut self, name: String) -> Self {
234        self.database_name = Some(name);
235        self
236    }
237}
238
239impl Default for VerifyConfig {
240    fn default() -> Self {
241        VerifyConfig {
242            verify_btree: true,
243            verify_log: true,
244            verify_data_checksums: true,
245            repair: false,
246            max_errors: 100,
247            verbose: false,
248            database_name: None,
249        }
250    }
251}
252
253// ============================================================================
254// Tree structural verification helpers
255// ============================================================================
256
257/// Verifies the structural integrity of a B-tree.
258///
259/// Walks the tree from root to BIN leaves and checks:
260///
261/// 1. Each upper IN's children are accessible (non-null child references).
262/// 2. For each IN, every child's leftmost key is >= the parent key entry that
263///    routes to it (key-range containment).
264/// 3. Each BIN entry that is not known-deleted has a valid (non-NULL) LSN.
265///
266/// Returns a `VerifyResult` with any anomalies found and the count of records
267/// verified.
268///
269///
270pub fn verify_tree(
271    tree: &Tree,
272    db_name: &str,
273    config: &VerifyConfig,
274) -> VerifyResult {
275    let mut result = VerifyResult::new();
276
277    if !config.verify_btree {
278        return result;
279    }
280
281    let root = match tree.get_root() {
282        Some(r) => r,
283        None => {
284            // Empty tree is valid.
285            result.databases_verified = 1;
286            return result;
287        }
288    };
289
290    let mut records: u64 = 0;
291    verify_node(&root, None, db_name, config, &mut result, &mut records);
292    result.records_verified = records;
293    result.databases_verified = 1;
294    result
295}
296
297/// Recursively verifies a tree node.
298fn verify_node(
299    node_arc: &Arc<RwLock<TreeNode>>,
300    parent_key: Option<&[u8]>,
301    db_name: &str,
302    config: &VerifyConfig,
303    result: &mut VerifyResult,
304    records: &mut u64,
305) {
306    let guard = node_arc.read();
307
308    match &*guard {
309        TreeNode::Internal(in_node) => {
310            verify_internal_node(
311                in_node, parent_key, db_name, config, result, records,
312            );
313        }
314        TreeNode::Bottom(bin_stub) => {
315            verify_bin_stub(
316                bin_stub, parent_key, db_name, config, result, records,
317            );
318        }
319    }
320}
321
322/// Verifies an upper internal node (IN).
323///
324/// `VerifyUtils.verifyIN()`: checks that each child's first key is
325/// within the key range implied by the parent entry.
326fn verify_internal_node(
327    in_node: &InNodeStub,
328    _parent_key: Option<&[u8]>,
329    db_name: &str,
330    config: &VerifyConfig,
331    result: &mut VerifyResult,
332    records: &mut u64,
333) {
334    if in_node.entries.is_empty() {
335        // An internal node with no entries is structurally empty but not
336        // necessarily an error (can occur transiently during splits).
337        return;
338    }
339
340    // Walk each child entry.
341    for (i, entry) in in_node.entries.iter().enumerate() {
342        let child_arc = match &entry.child {
343            Some(c) => c,
344            None => {
345                result.add_error(VerifyError::BtreeError {
346                    db_name: db_name.to_string(),
347                    description: format!(
348                        "IN node (id={}) entry {} has null child reference",
349                        in_node.node_id, i
350                    ),
351                });
352                if result.error_count() >= config.max_errors as usize {
353                    return;
354                }
355                continue;
356            }
357        };
358
359        // The key carried in slot 0 of an IN is the virtual -infinity key;
360        // entries at i > 0 carry the first key of that child's subtree.
361        // IN slot-0 special case.
362        let expected_parent_key: Option<&[u8]> =
363            if i == 0 { None } else { Some(entry.key.as_slice()) };
364
365        verify_node(
366            child_arc,
367            expected_parent_key,
368            db_name,
369            config,
370            result,
371            records,
372        );
373
374        if result.error_count() >= config.max_errors as usize {
375            return;
376        }
377    }
378}
379
380/// Verifies a BIN stub (leaf-level node).
381///
382/// `VerifyUtils.verifyBIN()`: checks that non-deleted slots carry
383/// valid (non-NULL) LSNs, and that the BIN's first key is >= the routing key
384/// passed from the parent.
385fn verify_bin_stub(
386    bin: &BinStub,
387    parent_key: Option<&[u8]>,
388    db_name: &str,
389    config: &VerifyConfig,
390    result: &mut VerifyResult,
391    records: &mut u64,
392) {
393    // Check that the BIN's first key is >= the routing key from the parent.
394    if let Some(pk) = parent_key
395        && !bin.entries.is_empty()
396    {
397        let first_full = bin.get_full_key(0);
398        if let Some(ref first_key) = first_full
399            && first_key.as_slice() < pk
400        {
401            result.add_error(VerifyError::BtreeError {
402                        db_name: db_name.to_string(),
403                        description: format!(
404                            "BIN (id={}) first key {:?} is less than parent routing key {:?}",
405                            bin.node_id, first_key, pk
406                        ),
407                    });
408        }
409    }
410
411    // Check each slot.
412    for (i, entry) in bin.entries.iter().enumerate() {
413        // Non-deleted entries must have a valid LSN.
414        if !entry.known_deleted && entry.lsn == NULL_LSN {
415            result.add_error(VerifyError::BtreeError {
416                db_name: db_name.to_string(),
417                description: format!(
418                    "BIN (id={}) slot {} has NULL LSN but is not known-deleted",
419                    bin.node_id, i
420                ),
421            });
422            if result.error_count() >= config.max_errors as usize {
423                return;
424            }
425        }
426
427        if !entry.known_deleted {
428            *records += 1;
429        }
430    }
431}
432
433// ============================================================================
434// Public verification entry points
435// ============================================================================
436
437/// Verify the environment.
438///
439/// Performs structural verification of the environment when a tree reference
440/// is available via `verify_tree()`.  This entry point operates without a
441/// live tree reference and therefore validates only configuration-level
442/// invariants; call `verify_tree()` directly to walk a B-tree.
443///
444///
445///
446/// # Arguments
447///
448/// * `config` - Configuration controlling what to verify.
449///
450/// # Returns
451///
452/// Verify all databases in the environment for structural integrity.
453///
454/// **Stub — not yet implemented.** This function always returns a passing
455/// `VerifyResult` without performing any verification work. The `verify_btree`,
456/// `verify_log`, `verify_data_checksums`, and `repair` fields of `VerifyConfig`
457/// are accepted but have no effect. Do not rely on the result for production
458/// integrity checking.
459///
460/// See `docs/src/operations/known-limitations.md` for the current status.
461///
462/// # Arguments
463///
464/// * `config` - Configuration controlling what to verify.
465///
466/// # Returns
467///
468/// A `VerifyResult` containing any errors found and verification statistics.
469pub fn verify_environment(config: &VerifyConfig) -> VerifyResult {
470    log::warn!(
471        "verify_environment called but verification is not yet implemented; \
472         the result does not reflect a real integrity check"
473    );
474    if config.verbose {
475        log::info!("Starting environment verification");
476        log::info!("  B-tree: {}", config.verify_btree);
477        log::info!("  Log: {}", config.verify_log);
478        log::info!("  Checksums: {}", config.verify_data_checksums);
479        log::info!("  Repair: {}", config.repair);
480    }
481
482    VerifyResult {
483        errors: Vec::new(),
484        warnings: Vec::new(),
485        databases_verified: 0,
486        records_verified: 0,
487        passed: true,
488    }
489}
490
491/// Verify a specific database by name.
492///
493/// **Stub — not yet implemented.** This function always returns a passing
494/// `VerifyResult` without performing any verification work. Do not rely on
495/// the result for production integrity checking.
496///
497/// When a live tree reference is available, call `verify_tree()` directly to
498/// perform full structural verification (key-range checks, LSN validity).
499/// This entry point validates database-level metadata without a tree handle.
500///
501/// See `docs/src/operations/known-limitations.md` for the current status.
502///
503/// # Arguments
504///
505/// * `db_name` - Name of the database to verify.
506/// * `config` - Configuration controlling what to verify.
507///
508/// # Returns
509///
510/// A `VerifyResult` containing any errors found and verification statistics.
511pub fn verify_database(db_name: &str, config: &VerifyConfig) -> VerifyResult {
512    log::warn!(
513        "verify_database({db_name}) called but verification is not yet implemented; \
514         the result does not reflect a real integrity check"
515    );
516    if config.verbose {
517        log::info!("Verifying database: {}", db_name);
518    }
519
520    VerifyResult {
521        errors: Vec::new(),
522        warnings: Vec::new(),
523        databases_verified: 1,
524        records_verified: 0,
525        passed: true,
526    }
527}
528
529/// Verify a `DatabaseImpl`'s B-tree structural integrity.
530///
531/// Calls `verify_tree()` on the underlying real B-tree when one is present.
532/// Used by `Database::verify()` in `noxu-db` to bridge the crate boundary
533/// (noxu-db does not depend directly on noxu-tree).
534///
535/// Mirrors `DatabaseImpl.verify(VerifyConfig)` in— calls BtreeVerifier
536/// on the tree owned by the DatabaseImpl.
537///
538/// # Arguments
539///
540/// * `db_impl` - The database implementation to verify.
541/// * `config` - Configuration controlling what to verify.
542///
543/// # Returns
544///
545/// A `VerifyResult` with structural errors and the count of records verified.
546pub fn verify_database_impl(
547    db_impl: &DatabaseImpl,
548    config: &VerifyConfig,
549) -> VerifyResult {
550    let db_name = db_impl.get_name();
551    match db_impl.get_real_tree() {
552        Some(tree) => verify_tree(&tree, db_name, config),
553        None => {
554            // No real B-tree attached (e.g., stub / metadata DB) — treat as empty.
555            VerifyResult {
556                errors: Vec::new(),
557                warnings: Vec::new(),
558                databases_verified: 1,
559                records_verified: 0,
560                passed: true,
561            }
562        }
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569
570    #[test]
571    fn test_verify_result_new() {
572        let result = VerifyResult::new();
573        assert!(result.passed);
574        assert_eq!(result.errors.len(), 0);
575        assert_eq!(result.warnings.len(), 0);
576        assert_eq!(result.databases_verified, 0);
577        assert_eq!(result.records_verified, 0);
578    }
579
580    #[test]
581    fn test_verify_result_default() {
582        let result = VerifyResult::default();
583        assert!(result.passed);
584        assert!(result.errors.is_empty());
585    }
586
587    #[test]
588    fn test_verify_result_with_errors() {
589        let errors = vec![VerifyError::BtreeError {
590            db_name: "test".to_string(),
591            description: "Invalid node".to_string(),
592        }];
593        let result = VerifyResult::with_errors(errors);
594        assert!(!result.passed);
595        assert_eq!(result.errors.len(), 1);
596    }
597
598    #[test]
599    fn test_verify_result_with_no_errors() {
600        let errors = vec![];
601        let result = VerifyResult::with_errors(errors);
602        assert!(result.passed);
603        assert_eq!(result.errors.len(), 0);
604    }
605
606    #[test]
607    fn test_add_error() {
608        let mut result = VerifyResult::new();
609        assert!(result.passed);
610
611        result.add_error(VerifyError::DataInconsistency {
612            description: "Test error".to_string(),
613        });
614
615        assert!(!result.passed);
616        assert_eq!(result.errors.len(), 1);
617    }
618
619    #[test]
620    fn test_add_warning() {
621        let mut result = VerifyResult::new();
622        result.add_warning("Test warning".to_string());
623
624        assert!(result.passed); // warnings don't affect passed status
625        assert_eq!(result.warnings.len(), 1);
626    }
627
628    #[test]
629    fn test_error_count() {
630        let mut result = VerifyResult::new();
631        assert_eq!(result.error_count(), 0);
632
633        result.add_error(VerifyError::DataInconsistency {
634            description: "Error 1".to_string(),
635        });
636        result.add_error(VerifyError::DataInconsistency {
637            description: "Error 2".to_string(),
638        });
639
640        assert_eq!(result.error_count(), 2);
641    }
642
643    #[test]
644    fn test_warning_count() {
645        let mut result = VerifyResult::new();
646        assert_eq!(result.warning_count(), 0);
647
648        result.add_warning("Warning 1".to_string());
649        result.add_warning("Warning 2".to_string());
650
651        assert_eq!(result.warning_count(), 2);
652    }
653
654    #[test]
655    fn test_is_passed() {
656        let result = VerifyResult::new();
657        assert!(result.is_passed());
658
659        let mut failed_result = VerifyResult::new();
660        failed_result.add_error(VerifyError::DataInconsistency {
661            description: "Error".to_string(),
662        });
663        assert!(!failed_result.is_passed());
664    }
665
666    #[test]
667    fn test_verify_error_btree() {
668        let error = VerifyError::BtreeError {
669            db_name: "mydb".to_string(),
670            description: "Invalid child reference".to_string(),
671        };
672        let s = format!("{}", error);
673        assert!(s.contains("B-tree error"));
674        assert!(s.contains("mydb"));
675        assert!(s.contains("Invalid child reference"));
676    }
677
678    #[test]
679    fn test_verify_error_log() {
680        let error = VerifyError::LogError {
681            file_number: 42,
682            description: "Corrupted entry".to_string(),
683        };
684        let s = format!("{}", error);
685        assert!(s.contains("Log file"));
686        assert!(s.contains("0000002a.ndb"));
687        assert!(s.contains("Corrupted entry"));
688    }
689
690    #[test]
691    fn test_verify_error_data_inconsistency() {
692        let error = VerifyError::DataInconsistency {
693            description: "Mismatched LSN".to_string(),
694        };
695        let s = format!("{}", error);
696        assert!(s.contains("Data inconsistency"));
697        assert!(s.contains("Mismatched LSN"));
698    }
699
700    #[test]
701    fn test_verify_error_checksum() {
702        let error = VerifyError::ChecksumError {
703            location: "file 10, offset 1024".to_string(),
704            description: "CRC mismatch".to_string(),
705        };
706        let s = format!("{}", error);
707        assert!(s.contains("Checksum error"));
708        assert!(s.contains("file 10, offset 1024"));
709        assert!(s.contains("CRC mismatch"));
710    }
711
712    #[test]
713    fn test_verify_error_invalid_node_reference() {
714        let error = VerifyError::InvalidNodeReference {
715            node_id: 12345,
716            description: "Node not found".to_string(),
717        };
718        let s = format!("{}", error);
719        assert!(s.contains("Invalid node reference"));
720        assert!(s.contains("12345"));
721        assert!(s.contains("Node not found"));
722    }
723
724    #[test]
725    fn test_verify_error_metadata() {
726        let error = VerifyError::MetadataError {
727            db_name: "testdb".to_string(),
728            description: "Invalid format version".to_string(),
729        };
730        let s = format!("{}", error);
731        assert!(s.contains("Metadata error"));
732        assert!(s.contains("testdb"));
733        assert!(s.contains("Invalid format version"));
734    }
735
736    #[test]
737    fn test_verify_config_default() {
738        let config = VerifyConfig::default();
739        assert!(config.verify_btree);
740        assert!(config.verify_log);
741        assert!(config.verify_data_checksums);
742        assert!(!config.repair);
743        assert_eq!(config.max_errors, 100);
744        assert!(!config.verbose);
745        assert!(config.database_name.is_none());
746    }
747
748    #[test]
749    fn test_verify_config_new() {
750        let config = VerifyConfig::new();
751        assert_eq!(config, VerifyConfig::default());
752    }
753
754    #[test]
755    fn test_verify_config_builder() {
756        let config = VerifyConfig::new()
757            .with_btree_verification(false)
758            .with_log_verification(true)
759            .with_checksum_verification(false)
760            .with_repair(true)
761            .with_max_errors(50)
762            .with_verbose(true)
763            .for_database("mydb".to_string());
764
765        assert!(!config.verify_btree);
766        assert!(config.verify_log);
767        assert!(!config.verify_data_checksums);
768        assert!(config.repair);
769        assert_eq!(config.max_errors, 50);
770        assert!(config.verbose);
771        assert_eq!(config.database_name, Some("mydb".to_string()));
772    }
773
774    #[test]
775    fn test_verify_environment_stub() {
776        let config = VerifyConfig::default();
777        let result = verify_environment(&config);
778        assert!(result.passed);
779        assert_eq!(result.errors.len(), 0);
780    }
781
782    #[test]
783    fn test_verify_environment_with_custom_config() {
784        let config = VerifyConfig::new().with_repair(true).with_max_errors(10);
785        let result = verify_environment(&config);
786        assert!(result.passed);
787    }
788
789    #[test]
790    fn test_verify_database_stub() {
791        let config = VerifyConfig::default();
792        let result = verify_database("testdb", &config);
793        assert!(result.passed);
794        assert_eq!(result.databases_verified, 1);
795    }
796
797    #[test]
798    fn test_verify_result_display_passed() {
799        let result = VerifyResult {
800            errors: Vec::new(),
801            warnings: Vec::new(),
802            databases_verified: 5,
803            records_verified: 1000,
804            passed: true,
805        };
806
807        let output = format!("{}", result);
808        assert!(output.contains("PASSED"));
809        assert!(output.contains("Databases verified: 5"));
810        assert!(output.contains("Records verified: 1000"));
811        assert!(output.contains("No errors or warnings"));
812    }
813
814    #[test]
815    fn test_verify_result_display_with_errors() {
816        let mut result = VerifyResult::new();
817        result.add_error(VerifyError::BtreeError {
818            db_name: "test".to_string(),
819            description: "Bad node".to_string(),
820        });
821        result.databases_verified = 2;
822        result.records_verified = 500;
823
824        let output = format!("{}", result);
825        assert!(output.contains("FAILED"));
826        assert!(output.contains("Errors (1)"));
827        assert!(output.contains("B-tree error"));
828    }
829
830    #[test]
831    fn test_verify_result_display_with_warnings() {
832        let mut result = VerifyResult::new();
833        result.add_warning("Low cache utilization".to_string());
834        result.databases_verified = 3;
835
836        let output = format!("{}", result);
837        assert!(output.contains("PASSED"));
838        assert!(output.contains("Warnings (1)"));
839        assert!(output.contains("Low cache utilization"));
840    }
841
842    #[test]
843    fn test_verify_result_clone() {
844        let mut result = VerifyResult::new();
845        result.add_error(VerifyError::DataInconsistency {
846            description: "Test".to_string(),
847        });
848
849        let cloned = result.clone();
850        assert_eq!(cloned.errors.len(), result.errors.len());
851        assert_eq!(cloned.passed, result.passed);
852    }
853
854    #[test]
855    fn test_verify_error_equality() {
856        let error1 = VerifyError::BtreeError {
857            db_name: "db1".to_string(),
858            description: "error".to_string(),
859        };
860        let error2 = VerifyError::BtreeError {
861            db_name: "db1".to_string(),
862            description: "error".to_string(),
863        };
864        let error3 = VerifyError::BtreeError {
865            db_name: "db2".to_string(),
866            description: "error".to_string(),
867        };
868
869        assert_eq!(error1, error2);
870        assert_ne!(error1, error3);
871    }
872
873    #[test]
874    fn test_verify_config_equality() {
875        let config1 = VerifyConfig::default();
876        let config2 = VerifyConfig::default();
877        let config3 = VerifyConfig::new().with_repair(true);
878
879        assert_eq!(config1, config2);
880        assert_ne!(config1, config3);
881    }
882
883    // ── verify_tree tests ────────────────────────────────────────────────────
884
885    /// verify_tree on an empty tree returns a passing result.
886    #[test]
887    fn test_verify_tree_empty() {
888        use noxu_dbi::{DatabaseConfig, DatabaseId, DatabaseImpl, DbType};
889        use noxu_sync::RwLock;
890        use std::sync::Arc;
891
892        let db_id = DatabaseId::new(1);
893        let config = DatabaseConfig::default();
894        let db_impl = DatabaseImpl::new(
895            db_id,
896            "verify_test".to_string(),
897            DbType::User,
898            &config,
899        );
900        let db = Arc::new(RwLock::new(db_impl));
901        let guard = db.read();
902        let cfg = VerifyConfig::default();
903
904        if let Some(t) = guard.get_real_tree() {
905            let result = verify_tree(&t, "verify_test", &cfg);
906            assert!(
907                result.passed,
908                "empty tree should pass: {:?}",
909                result.errors
910            );
911            assert_eq!(result.databases_verified, 1);
912        }
913        // If no real tree is present the test is a no-op.
914    }
915
916    /// verify_tree on a populated tree returns a passing result.
917    ///
918    /// Uses a real LogManager so that each put() receives a valid (non-NULL)
919    /// LSN — the verifier requires this for all non-deleted BIN entries.
920    #[test]
921    fn test_verify_tree_populated() {
922        use noxu_dbi::{
923            CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
924            PutMode,
925        };
926        use noxu_log::{FileManager, LogManager};
927        use noxu_sync::RwLock;
928        use std::sync::Arc;
929        use tempfile::TempDir;
930
931        let dir = TempDir::new().unwrap();
932        let fm = Arc::new(
933            FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
934        );
935        let lm =
936            Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
937
938        let db_id = DatabaseId::new(2);
939        let config = DatabaseConfig::default();
940        let db_impl = DatabaseImpl::new(
941            db_id,
942            "pop_test".to_string(),
943            DbType::User,
944            &config,
945        );
946        let db = Arc::new(RwLock::new(db_impl));
947
948        {
949            let mut cursor = CursorImpl::with_log_manager(
950                Arc::clone(&db),
951                1,
952                Arc::clone(&lm),
953            );
954            cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
955            cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
956            cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
957        }
958
959        let guard = db.read();
960        let cfg = VerifyConfig::default();
961
962        if let Some(t) = guard.get_real_tree() {
963            let result = verify_tree(&t, "pop_test", &cfg);
964            assert!(
965                result.passed,
966                "populated tree should pass: {:?}",
967                result.errors
968            );
969            assert_eq!(result.databases_verified, 1);
970        }
971    }
972}