use serial_test::serial;
use std::path::PathBuf;
use std::time::Duration;
use tokio::time::timeout;
use tracing::Level;
use terraphim_config::{ConfigBuilder, ConfigId, ConfigState, KnowledgeGraph};
use terraphim_service::TerraphimService;
use terraphim_types::{KnowledgeGraphInputType, NormalizedTermValue, RoleName, SearchQuery};
#[tokio::test]
#[serial]
async fn test_thesaurus_full_persistence_lifecycle() {
let _ = tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.try_init();
println!("๐งช Testing complete thesaurus persistence lifecycle");
println!("๐ Step 1: Initializing memory-only persistence");
terraphim_persistence::DeviceStorage::init_memory_only()
.await
.expect("Failed to initialize memory-only persistence");
println!("๐ง Step 2: Creating desktop configuration");
let mut config = ConfigBuilder::new_with_id(ConfigId::Desktop)
.build_default_desktop()
.build()
.expect("Failed to build desktop config");
let project_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
println!(" Current working directory: {:?}", project_root);
let kg_path = if project_root.ends_with("crates") {
project_root.join("../docs/src/kg")
} else {
project_root.join("docs/src/kg")
};
if !kg_path.exists() {
println!("โ ๏ธ KG directory not found at {:?}, skipping test", kg_path);
return;
}
let role_name = RoleName::new("Terraphim Engineer");
if let Some(role) = config.roles.get_mut(&role_name) {
role.kg = Some(KnowledgeGraph {
automata_path: None,
knowledge_graph_local: Some(terraphim_config::KnowledgeGraphLocal {
input_type: KnowledgeGraphInputType::Markdown,
path: kg_path,
}),
public: false,
publish: false,
});
}
let config_state = ConfigState::new(&mut config)
.await
.expect("Failed to initialize ConfigState");
println!("๐ Step 3: Creating TerraphimService and loading thesaurus");
let mut terraphim_service = TerraphimService::new(config_state.clone());
println!(" ๐ First load: Building thesaurus from KG files");
let first_load_result = timeout(
Duration::from_secs(60),
terraphim_service.ensure_thesaurus_loaded(&role_name),
)
.await
.expect("First thesaurus load timed out")
.expect("First thesaurus load failed");
println!(
" โ
First load succeeded: {} entries",
first_load_result.len()
);
assert!(
!first_load_result.is_empty(),
"Thesaurus should not be empty after building from KG"
);
let expected_terms = vec!["haystack", "service", "terraphim-graph"];
for term in &expected_terms {
let normalized_term = NormalizedTermValue::from(term.to_string());
if first_load_result.get(&normalized_term).is_some() {
println!(" โ Found expected term: '{}'", term);
} else {
println!(" โ ๏ธ Missing expected term: '{}'", term);
}
}
println!("๐ Step 4: Testing persistence with new service instance");
let mut new_service = TerraphimService::new(config_state.clone());
println!(" ๐ Second load: Loading thesaurus from persistence");
let second_load_result = timeout(
Duration::from_secs(30),
new_service.ensure_thesaurus_loaded(&role_name),
)
.await
.expect("Second thesaurus load timed out")
.expect("Second thesaurus load failed");
println!(
" โ
Second load succeeded: {} entries",
second_load_result.len()
);
println!("๐ Step 5: Verifying consistency between loads");
assert_eq!(
first_load_result.len(),
second_load_result.len(),
"Thesaurus should have same number of entries after persistence"
);
for term in &expected_terms {
let normalized_term = NormalizedTermValue::from(term.to_string());
let first_has = first_load_result.get(&normalized_term).is_some();
let second_has = second_load_result.get(&normalized_term).is_some();
if first_has {
assert!(second_has, "Term '{}' should persist between loads", term);
println!(" โ Term '{}' persisted correctly", term);
}
}
println!("๐ Step 6: Testing thesaurus functionality with search");
let search_query = SearchQuery {
search_term: "haystack".into(),
role: Some(role_name.clone()),
skip: None,
limit: Some(5),
..Default::default()
};
let search_result = timeout(Duration::from_secs(30), new_service.search(&search_query))
.await
.expect("Search timed out")
.expect("Search failed");
println!(
" ๐ Search with persisted thesaurus: {} results",
search_result.len()
);
println!("๐ Step 7: Verifying rolegraph in config_state");
let config_data = new_service.fetch_config().await;
assert!(
config_data.roles.contains_key(&role_name),
"Terraphim Engineer role should exist in config"
);
let terraphim_role = &config_data.roles[&role_name];
assert_eq!(terraphim_role.name, role_name, "Role name should match");
println!("๐ All persistence tests passed!");
println!("โ
Thesaurus builds correctly from KG files");
println!("โ
Thesaurus persists correctly to storage");
println!("โ
Thesaurus loads correctly from persistence");
println!("โ
Thesaurus maintains consistency across loads");
println!("โ
Search functionality works with persisted thesaurus");
}
#[tokio::test]
#[serial]
async fn test_thesaurus_persistence_error_handling() {
println!("๐งช Testing thesaurus persistence error handling");
let _ = tracing_subscriber::fmt()
.with_max_level(Level::WARN)
.try_init();
terraphim_persistence::DeviceStorage::init_memory_only()
.await
.expect("Failed to initialize memory-only persistence");
let mut config = ConfigBuilder::new_with_id(ConfigId::Desktop)
.build_default_desktop()
.build()
.expect("Failed to build desktop config");
let project_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let kg_path = if project_root.ends_with("crates") {
project_root.join("../docs/src/kg")
} else {
project_root.join("docs/src/kg")
};
let role_name = RoleName::new("Terraphim Engineer");
if kg_path.exists() {
if let Some(role) = config.roles.get_mut(&role_name) {
role.kg = Some(KnowledgeGraph {
automata_path: None,
knowledge_graph_local: Some(terraphim_config::KnowledgeGraphLocal {
input_type: KnowledgeGraphInputType::Markdown,
path: kg_path,
}),
public: false,
publish: false,
});
}
}
let config_state = ConfigState::new(&mut config)
.await
.expect("Failed to initialize ConfigState");
let mut service = TerraphimService::new(config_state);
println!(" ๐ Testing with invalid role name");
let invalid_role = RoleName::new("NonExistent Role");
let result = service.ensure_thesaurus_loaded(&invalid_role).await;
match result {
Ok(_) => println!(" โ ๏ธ Unexpected success with invalid role"),
Err(e) => {
println!(" โ
Correctly handled invalid role: {:?}", e);
}
}
println!(" ๐ Testing with valid role");
let valid_role = RoleName::new("Terraphim Engineer");
let result = service.ensure_thesaurus_loaded(&valid_role).await;
match result {
Ok(thesaurus) => {
println!(
" โ
Successfully loaded thesaurus: {} entries",
thesaurus.len()
);
}
Err(e) => {
println!(" โ Failed to load thesaurus: {:?}", e);
}
}
println!("โ
Error handling test completed");
}
#[tokio::test]
#[serial]
async fn test_thesaurus_memory_vs_persistence() {
println!("๐งช Testing thesaurus memory vs persistence behavior");
let _ = tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.try_init();
terraphim_persistence::DeviceStorage::init_memory_only()
.await
.expect("Failed to initialize memory-only persistence");
let mut config = ConfigBuilder::new_with_id(ConfigId::Desktop)
.build_default_desktop()
.build()
.expect("Failed to build desktop config");
let project_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let kg_path = if project_root.ends_with("crates") {
project_root.join("../docs/src/kg")
} else {
project_root.join("docs/src/kg")
};
let role_name = RoleName::new("Terraphim Engineer");
if kg_path.exists() {
if let Some(role) = config.roles.get_mut(&role_name) {
role.kg = Some(KnowledgeGraph {
automata_path: None,
knowledge_graph_local: Some(terraphim_config::KnowledgeGraphLocal {
input_type: KnowledgeGraphInputType::Markdown,
path: kg_path,
}),
public: false,
publish: false,
});
}
}
let config_state = ConfigState::new(&mut config)
.await
.expect("Failed to initialize ConfigState");
let mut service = TerraphimService::new(config_state);
println!(" ๐ Testing multiple loads for stability");
for i in 1..=3 {
println!(" ๐ Load attempt {}", i);
let result = timeout(
Duration::from_secs(30),
service.ensure_thesaurus_loaded(&role_name),
)
.await
.expect("Load timed out")
.expect("Load failed");
println!(" โ
Load {} succeeded: {} entries", i, result.len());
if result.is_empty() {
println!(" โ ๏ธ Thesaurus is empty - may be due to test isolation issues");
}
let haystack_term = NormalizedTermValue::from("haystack".to_string());
let service_term = NormalizedTermValue::from("service".to_string());
if result.get(&haystack_term).is_some() {
println!(" โ Contains 'haystack'");
}
if result.get(&service_term).is_some() {
println!(" โ Contains 'service'");
}
}
println!("โ
Memory persistence stability test passed");
}