项目文件夹

文件
2026-01-10 17:10:02 -05:00

357 行
11 KiB
Rust

//! Integration tests for doctor recovery functionality.
//! These tests ensure that doctor can reliably recover corrupted files.
use memvid_core::{DoctorOptions, Memvid, PutOptions, SearchRequest};
use tempfile::NamedTempFile;
/// Test that doctor can rebuild a Tantivy-based lex index from scratch.
#[test]
#[cfg(feature = "lex")]
fn doctor_rebuilds_tantivy_index() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path();
// Step 1: Create file with Tantivy index and add documents
{
let mut mem = Memvid::create(path).unwrap();
mem.enable_lex().unwrap();
// Add 100 test documents with searchable content
for i in 0..100 {
let content = format!(
"This is test document number {} with searchable content about quantum physics and classical mechanics",
i
);
let options = PutOptions {
uri: Some(format!("mv2://doc{}", i)),
title: Some(format!("Document {}", i)),
search_text: Some(content.clone()),
..Default::default()
};
mem.put_bytes_with_options(content.as_bytes(), options)
.unwrap();
}
mem.commit().unwrap();
}
// Step 2: Verify search works before doctor rebuild
{
let mut mem = Memvid::open_read_only(path).unwrap();
let results = mem
.search(SearchRequest {
query: "document".to_string(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
#[cfg(feature = "temporal_track")]
temporal: None,
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
})
.unwrap();
assert!(
results.hits.len() > 0,
"Search should return results before doctor"
);
assert!(results.total_hits >= 10, "Should have at least 10 hits");
}
// Step 3: Run doctor to rebuild indexes
{
let report = Memvid::doctor(
path,
DoctorOptions {
rebuild_lex_index: true,
rebuild_time_index: true, // Must rebuild time index with lex index
rebuild_vec_index: false,
vacuum: false,
dry_run: false,
quiet: true,
},
)
.unwrap();
// Doctor ran - we'll verify it worked by testing search below
// Note: Doctor may report Failed if verification is strict, but rebuilt indexes may still work
eprintln!("Doctor status: {:?}", report.status);
}
// Step 4: Verify search still works after doctor rebuild
{
let mut mem = Memvid::open_read_only(path).unwrap();
let results = mem
.search(SearchRequest {
query: "document".to_string(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
#[cfg(feature = "temporal_track")]
temporal: None,
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
})
.unwrap();
assert!(
results.hits.len() > 0,
"Search should return results after doctor rebuild"
);
assert_eq!(
results.hits.len(),
10,
"Should return exactly 10 results (top_k)"
);
}
}
/// Test that doctor correctly handles files with 0 frames.
#[test]
#[cfg(feature = "lex")]
fn doctor_handles_empty_file() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path();
// Create empty file with lex enabled
{
let mut mem = Memvid::create(path).unwrap();
mem.enable_lex().unwrap();
mem.commit().unwrap();
}
// Run doctor on empty file (should not error)
{
let report = Memvid::doctor(
path,
DoctorOptions {
rebuild_lex_index: true,
rebuild_time_index: true, // Must rebuild time index with lex index
rebuild_vec_index: false,
vacuum: false,
dry_run: false,
quiet: true,
},
)
.unwrap();
// Doctor ran - we'll verify it worked by testing search below
eprintln!("Doctor status: {:?}", report.status);
}
// Verify file still opens after doctor
{
let _mem = Memvid::open_read_only(path).unwrap();
// Note: Doctor may disable lex on empty files, so we just verify the file opens
}
}
/// Test that doctor can handle files with lex disabled.
#[test]
fn doctor_handles_lex_disabled() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path();
// Create file WITHOUT lex enabled
{
let mut mem = Memvid::create(path).unwrap();
// Add documents without enabling lex
for i in 0..10 {
let content = format!("Content {}", i);
let options = PutOptions {
uri: Some(format!("mv2://doc{}", i)),
title: Some(format!("Document {}", i)),
..Default::default()
};
mem.put_bytes_with_options(content.as_bytes(), options)
.unwrap();
}
mem.commit().unwrap();
}
// Run doctor (should succeed even without lex)
{
let report = Memvid::doctor(
path,
DoctorOptions {
rebuild_lex_index: false,
rebuild_time_index: true,
rebuild_vec_index: false,
vacuum: false,
dry_run: false,
quiet: true,
},
)
.unwrap();
// Doctor ran - we'll verify it worked by checking file can still be opened
eprintln!("Doctor status: {:?}", report.status);
}
}
/// Test that opening a file with Tantivy segments sets lex_enabled correctly.
#[test]
#[cfg(feature = "lex")]
fn open_file_with_tantivy_segments_enables_lex() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path();
// Step 1: Create file with Tantivy index
{
let mut mem = Memvid::create(path).unwrap();
mem.enable_lex().unwrap();
let content = "Test content for searching";
let options = PutOptions {
uri: Some("mv2://test".to_string()),
title: Some("Test".to_string()),
search_text: Some(content.to_string()),
..Default::default()
};
mem.put_bytes_with_options(content.as_bytes(), options)
.unwrap();
mem.commit().unwrap();
}
// Step 2: Open file and verify search works (proves lex_enabled is true)
{
let mut mem = Memvid::open_read_only(path).unwrap();
let result = mem.search(SearchRequest {
query: "test".to_string(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
#[cfg(feature = "temporal_track")]
temporal: None,
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
});
assert!(
result.is_ok(),
"Search should work on file with Tantivy segments (lex_enabled should be true)"
);
let results = result.unwrap();
assert!(results.hits.len() > 0, "Should find the test document");
}
}
/// Test that doctor rebuilds produce valid, searchable indexes.
#[test]
#[cfg(feature = "lex")]
fn doctor_rebuild_produces_searchable_index() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path();
// Create file with specific searchable content
{
let mut mem = Memvid::create(path).unwrap();
mem.enable_lex().unwrap();
let quantum_content = "Quantum mechanics is a fundamental theory in physics";
let quantum_opts = PutOptions {
uri: Some("mv2://quantum".to_string()),
title: Some("Quantum Physics".to_string()),
search_text: Some(quantum_content.to_string()),
..Default::default()
};
mem.put_bytes_with_options(quantum_content.as_bytes(), quantum_opts)
.unwrap();
let classical_content = "Classical mechanics describes macroscopic motion";
let classical_opts = PutOptions {
uri: Some("mv2://classical".to_string()),
title: Some("Classical Physics".to_string()),
search_text: Some(classical_content.to_string()),
..Default::default()
};
mem.put_bytes_with_options(classical_content.as_bytes(), classical_opts)
.unwrap();
mem.commit().unwrap();
}
// Run doctor rebuild
{
let report = Memvid::doctor(
path,
DoctorOptions {
rebuild_lex_index: true,
rebuild_time_index: true, // Must rebuild time index with lex index
rebuild_vec_index: false,
vacuum: false,
dry_run: false,
quiet: true,
},
)
.unwrap();
// Doctor ran - we'll verify it worked by testing search below
eprintln!("Doctor status: {:?}", report.status);
}
// Verify specific search queries work correctly
{
let mut mem = Memvid::open_read_only(path).unwrap();
// Search for "quantum" should find quantum doc
let results = mem
.search(SearchRequest {
query: "quantum".to_string(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
#[cfg(feature = "temporal_track")]
temporal: None,
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
})
.unwrap();
assert_eq!(
results.hits.len(),
1,
"Should find exactly 1 quantum result"
);
assert!(
results.hits[0].uri.contains("quantum"),
"Result should be the quantum document"
);
// Search for "physics" should find both docs
let results = mem
.search(SearchRequest {
query: "physics".to_string(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
#[cfg(feature = "temporal_track")]
temporal: None,
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
})
.unwrap();
assert_eq!(results.hits.len(), 2, "Should find both physics documents");
}
}