use super::*;
use crate::service::persistence::{data_dir, save_index_registry, PersistedIndex};
use serial_test::serial;
use std::path::PathBuf;
use tempfile::tempdir;
#[test]
#[serial]
fn migrate_storage_moves_files_and_updates_registry() {
let data_dir_tmp = tempdir().unwrap();
unsafe {
std::env::set_var("TRUSTY_DATA_DIR", data_dir_tmp.path());
}
let project_root = tempdir().unwrap();
let index_id = "test-migrate-idx";
let legacy_dir = data_dir().unwrap().join("indexes").join(index_id);
std::fs::create_dir_all(&legacy_dir).unwrap();
std::fs::write(legacy_dir.join("index.redb"), b"redb-sentinel").unwrap();
std::fs::write(legacy_dir.join("hnsw.usearch"), b"hnsw-sentinel").unwrap();
std::fs::write(legacy_dir.join("schema_version.json"), b"{}").unwrap();
save_index_registry(&[PersistedIndex {
id: index_id.to_string(),
root_path: project_root.path().to_path_buf(),
colocated: false,
..Default::default()
}])
.unwrap();
handle_migrate_storage(false).unwrap();
let colocated = project_root.path().join(".trusty-search");
assert!(
colocated.exists(),
"colocated dir must exist after migration"
);
assert!(
colocated.join("index.redb").exists(),
"index.redb must be in colocated dir"
);
assert!(
colocated.join("hnsw.usearch").exists(),
"hnsw.usearch must be in colocated dir"
);
assert!(
colocated.join("schema_version.json").exists(),
"schema_version.json must be in colocated dir"
);
assert!(
!legacy_dir.exists() || std::fs::read_dir(&legacy_dir).unwrap().next().is_none(),
"legacy dir must be empty or deleted after migration"
);
let roots = crate::service::roots_registry::load_roots().unwrap();
assert!(
roots.iter().any(|r| r.path == project_root.path()),
"root must be registered in roots.toml after migration"
);
let gitignore_content =
std::fs::read_to_string(project_root.path().join(".gitignore")).unwrap_or_default();
assert!(
gitignore_content.contains(".trusty-search/"),
".gitignore must contain .trusty-search/ after migration"
);
let entries = load_index_registry().unwrap();
assert_eq!(entries.len(), 1);
assert!(
entries[0].colocated,
"registry must be updated to colocated=true after migration"
);
assert_eq!(
std::fs::read(colocated.join("index.redb")).unwrap(),
b"redb-sentinel"
);
unsafe {
std::env::remove_var("TRUSTY_DATA_DIR");
}
}
#[test]
#[serial]
fn handle_legacy_pointer_file_migrates_correctly() {
let data_dir_tmp = tempdir().unwrap();
unsafe {
std::env::set_var("TRUSTY_DATA_DIR", data_dir_tmp.path());
}
let project_root = tempdir().unwrap();
let index_id = "ptr-regress";
let pointer = project_root.path().join(".trusty-search");
std::fs::write(&pointer, b"index = \"ptr-regress\"").unwrap();
assert!(pointer.is_file(), "pointer must be a file before migration");
let src = data_dir().unwrap().join("indexes").join(index_id);
std::fs::create_dir_all(&src).unwrap();
std::fs::write(src.join("index.redb"), b"ptr-redb-data").unwrap();
std::fs::write(src.join("hnsw.usearch"), b"ptr-hnsw-data").unwrap();
save_index_registry(&[PersistedIndex {
id: index_id.to_string(),
root_path: project_root.path().to_path_buf(),
colocated: true, ..Default::default()
}])
.unwrap();
handle_migrate_storage(false).unwrap();
assert!(
!pointer.is_file(),
"legacy pointer file must be removed by migration"
);
let colocated = project_root.path().join(".trusty-search");
assert!(
colocated.is_dir(),
".trusty-search must be a directory post-migration"
);
assert!(
colocated.join("index.redb").exists(),
"index.redb must be present in colocated dir"
);
assert_eq!(
std::fs::read(colocated.join("index.redb")).unwrap(),
b"ptr-redb-data",
"index.redb content must survive migration"
);
unsafe {
std::env::remove_var("TRUSTY_DATA_DIR");
}
}
#[test]
#[serial]
fn migrate_storage_skips_missing_root() {
let data_dir_tmp = tempdir().unwrap();
unsafe {
std::env::set_var("TRUSTY_DATA_DIR", data_dir_tmp.path());
}
let nonexistent_root = PathBuf::from("/tmp/trusty-test-missing-root-12345");
save_index_registry(&[PersistedIndex {
id: "gone-index".to_string(),
root_path: nonexistent_root.clone(),
colocated: false,
..Default::default()
}])
.unwrap();
handle_migrate_storage(false).unwrap();
let entries = load_index_registry().unwrap();
assert_eq!(entries.len(), 1);
assert!(
!entries[0].colocated,
"missing-root index must not be marked colocated"
);
unsafe {
std::env::remove_var("TRUSTY_DATA_DIR");
}
}
#[test]
#[serial]
fn migrate_storage_idempotent_for_colocated() {
let data_dir_tmp = tempdir().unwrap();
unsafe {
std::env::set_var("TRUSTY_DATA_DIR", data_dir_tmp.path());
}
let project_root = tempdir().unwrap();
let colocated_dir = project_root.path().join(".trusty-search");
std::fs::create_dir_all(&colocated_dir).unwrap();
std::fs::write(colocated_dir.join("index.redb"), b"already-done").unwrap();
save_index_registry(&[PersistedIndex {
id: "col-index".to_string(),
root_path: project_root.path().to_path_buf(),
colocated: true,
..Default::default()
}])
.unwrap();
handle_migrate_storage(false).unwrap();
assert_eq!(
std::fs::read(colocated_dir.join("index.redb")).unwrap(),
b"already-done"
);
let entries = load_index_registry().unwrap();
assert_eq!(entries.len(), 1);
assert!(
entries[0].colocated,
"already-colocated must remain colocated"
);
unsafe {
std::env::remove_var("TRUSTY_DATA_DIR");
}
}
#[test]
#[serial]
fn migrate_storage_dry_run_no_changes() {
let data_dir_tmp = tempdir().unwrap();
unsafe {
std::env::set_var("TRUSTY_DATA_DIR", data_dir_tmp.path());
}
let project_root = tempdir().unwrap();
let index_id = "dry-run-test";
let legacy_dir = data_dir().unwrap().join("indexes").join(index_id);
std::fs::create_dir_all(&legacy_dir).unwrap();
std::fs::write(legacy_dir.join("index.redb"), b"dry-data").unwrap();
save_index_registry(&[PersistedIndex {
id: index_id.to_string(),
root_path: project_root.path().to_path_buf(),
colocated: false,
..Default::default()
}])
.unwrap();
handle_migrate_storage(true).unwrap();
assert!(
legacy_dir.join("index.redb").exists(),
"dry-run must not move source files"
);
assert!(
!project_root.path().join(".trusty-search").exists(),
"dry-run must not create colocated dir"
);
let entries = load_index_registry().unwrap();
assert!(!entries[0].colocated, "dry-run must not update registry");
unsafe {
std::env::remove_var("TRUSTY_DATA_DIR");
}
}