use crate::db::{create_pool, run_migrations};
use crate::error::{IntentError, Result};
use crate::global_projects;
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;
use std::path::PathBuf;
const INTENT_DIR: &str = ".intent-engine";
const DB_FILE: &str = "project.db";
const PROJECT_ROOT_MARKERS: &[&str] = &[
".git", ".hg", "package.json", "Cargo.toml", "pyproject.toml", "go.mod", "pom.xml", "build.gradle", ];
#[derive(Debug)]
pub struct ProjectContext {
pub root: PathBuf,
pub db_path: PathBuf,
pub pool: SqlitePool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirectoryTraversalInfo {
pub path: String,
pub has_intent_engine: bool,
pub is_selected: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DatabasePathInfo {
pub current_working_directory: String,
pub env_var_set: bool,
pub env_var_path: Option<String>,
pub env_var_valid: Option<bool>,
pub directories_checked: Vec<DirectoryTraversalInfo>,
pub home_directory: Option<String>,
pub home_has_intent_engine: bool,
pub final_database_path: Option<String>,
pub resolution_method: Option<String>,
}
impl ProjectContext {
pub fn get_database_path_info() -> DatabasePathInfo {
let cwd = std::env::current_dir()
.ok()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<unable to determine>".to_string());
let mut info = DatabasePathInfo {
current_working_directory: cwd.clone(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: Vec::new(),
home_directory: None,
home_has_intent_engine: false,
final_database_path: None,
resolution_method: None,
};
if let Ok(env_path) = std::env::var("INTENT_ENGINE_PROJECT_DIR") {
info.env_var_set = true;
info.env_var_path = Some(env_path.clone());
let path = PathBuf::from(&env_path);
let intent_dir = path.join(INTENT_DIR);
let has_intent_engine = intent_dir.exists() && intent_dir.is_dir();
info.env_var_valid = Some(has_intent_engine);
}
if let Ok(mut current) = std::env::current_dir() {
loop {
let intent_dir = current.join(INTENT_DIR);
let has_intent_engine = intent_dir.exists() && intent_dir.is_dir();
let is_selected = has_intent_engine && info.final_database_path.is_none();
info.directories_checked.push(DirectoryTraversalInfo {
path: current.display().to_string(),
has_intent_engine,
is_selected,
});
if has_intent_engine && info.final_database_path.is_none() {
let db_path = intent_dir.join(DB_FILE);
info.final_database_path = Some(db_path.display().to_string());
info.resolution_method = Some("Upward Directory Traversal".to_string());
}
if !current.pop() {
break;
}
}
}
#[cfg(not(target_os = "windows"))]
let home_path = std::env::var("HOME").ok().map(PathBuf::from);
#[cfg(target_os = "windows")]
let home_path = std::env::var("HOME")
.ok()
.map(PathBuf::from)
.or_else(|| std::env::var("USERPROFILE").ok().map(PathBuf::from));
if let Some(home) = home_path {
info.home_directory = Some(home.display().to_string());
let intent_dir = home.join(INTENT_DIR);
info.home_has_intent_engine = intent_dir.exists() && intent_dir.is_dir();
if info.home_has_intent_engine && info.final_database_path.is_none() {
let db_path = intent_dir.join(DB_FILE);
info.final_database_path = Some(db_path.display().to_string());
info.resolution_method = Some("Home Directory Fallback".to_string());
}
}
info
}
pub fn find_project_root() -> Option<PathBuf> {
if let Ok(current_dir) = std::env::current_dir() {
let start_dir = current_dir.clone();
let project_boundary = Self::infer_project_root();
let mut current = start_dir.clone();
loop {
let intent_dir = current.join(INTENT_DIR);
if intent_dir.exists() && intent_dir.is_dir() {
if let Some(ref boundary) = project_boundary {
if !current.starts_with(boundary) && current != *boundary {
break;
}
}
if current != start_dir {
eprintln!("✓ Found project: {}", current.display());
}
return Some(current);
}
if let Some(ref boundary) = project_boundary {
if current == *boundary {
eprintln!("✓ Detected project root: {}", boundary.display());
return Some(boundary.clone());
}
}
if !current.pop() {
break;
}
}
}
if let Ok(home) = std::env::var("HOME") {
let home_path = PathBuf::from(home);
let intent_dir = home_path.join(INTENT_DIR);
if intent_dir.exists() && intent_dir.is_dir() {
eprintln!("✓ Using home project: {}", home_path.display());
return Some(home_path);
}
}
#[cfg(target_os = "windows")]
if let Ok(userprofile) = std::env::var("USERPROFILE") {
let home_path = PathBuf::from(userprofile);
let intent_dir = home_path.join(INTENT_DIR);
if intent_dir.exists() && intent_dir.is_dir() {
eprintln!("✓ Using home project: {}", home_path.display());
return Some(home_path);
}
}
None
}
fn infer_project_root_from(start_path: &std::path::Path) -> Option<PathBuf> {
let mut current = start_path.to_path_buf();
loop {
for marker in PROJECT_ROOT_MARKERS {
let marker_path = current.join(marker);
if marker_path.exists() {
return Some(current);
}
}
if !current.pop() {
break;
}
}
None
}
fn infer_project_root() -> Option<PathBuf> {
let cwd = std::env::current_dir().ok()?;
Self::infer_project_root_from(&cwd)
}
pub async fn initialize_project() -> Result<Self> {
let cwd = std::env::current_dir()?;
let root = match Self::infer_project_root() {
Some(inferred_root) => {
inferred_root
},
None => {
eprintln!(
"Warning: Could not determine a project root based on common markers (e.g., .git, package.json).\n\
Initialized Intent-Engine in the current directory '{}'.\n\
For predictable behavior, it's recommended to initialize from a directory containing a root marker.",
cwd.display()
);
cwd
},
};
let intent_dir = root.join(INTENT_DIR);
let db_path = intent_dir.join(DB_FILE);
if !intent_dir.exists() {
std::fs::create_dir_all(&intent_dir)?;
}
let pool = create_pool(&db_path).await?;
run_migrations(&pool).await?;
Ok(ProjectContext {
root,
db_path,
pool,
})
}
pub async fn initialize_project_at(project_dir: PathBuf) -> Result<Self> {
let root = project_dir;
let intent_dir = root.join(INTENT_DIR);
let db_path = intent_dir.join(DB_FILE);
if !intent_dir.exists() {
std::fs::create_dir_all(&intent_dir)?;
}
let pool = create_pool(&db_path).await?;
run_migrations(&pool).await?;
Ok(ProjectContext {
root,
db_path,
pool,
})
}
pub async fn load() -> Result<Self> {
let root = Self::find_project_root().ok_or(IntentError::NotAProject)?;
let intent_dir = root.join(INTENT_DIR);
if !intent_dir.exists() || !intent_dir.is_dir() {
return Err(IntentError::NotAProject);
}
let db_path = intent_dir.join(DB_FILE);
let pool = create_pool(&db_path).await?;
run_migrations(&pool).await?;
Ok(ProjectContext {
root,
db_path,
pool,
})
}
pub async fn load_or_init() -> Result<Self> {
let ctx = match Self::load().await {
Ok(ctx) => ctx,
Err(IntentError::NotAProject) => Self::initialize_project().await?,
Err(e) => return Err(e),
};
global_projects::register_project(&ctx.root);
Ok(ctx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constants() {
assert_eq!(INTENT_DIR, ".intent-engine");
assert_eq!(DB_FILE, "project.db");
}
#[test]
fn test_project_context_debug() {
let _type_check = |ctx: ProjectContext| {
let _ = format!("{:?}", ctx);
};
}
#[test]
fn test_project_root_markers_list() {
assert!(PROJECT_ROOT_MARKERS.contains(&".git"));
assert!(PROJECT_ROOT_MARKERS.contains(&"Cargo.toml"));
assert!(PROJECT_ROOT_MARKERS.contains(&"package.json"));
}
#[test]
fn test_project_root_markers_priority() {
assert_eq!(PROJECT_ROOT_MARKERS[0], ".git");
}
#[test]
fn test_infer_project_root_with_git() {
assert!(PROJECT_ROOT_MARKERS.contains(&".git"));
}
#[test]
fn test_all_major_project_types_covered() {
let markers = PROJECT_ROOT_MARKERS;
assert!(markers.contains(&".git"));
assert!(markers.contains(&".hg"));
assert!(markers.contains(&"Cargo.toml")); assert!(markers.contains(&"package.json")); assert!(markers.contains(&"pyproject.toml")); assert!(markers.contains(&"go.mod")); assert!(markers.contains(&"pom.xml")); assert!(markers.contains(&"build.gradle")); }
#[test]
fn test_directory_traversal_info_creation() {
let info = DirectoryTraversalInfo {
path: "/test/path".to_string(),
has_intent_engine: true,
is_selected: false,
};
assert_eq!(info.path, "/test/path");
assert!(info.has_intent_engine);
assert!(!info.is_selected);
}
#[test]
fn test_directory_traversal_info_clone() {
let info = DirectoryTraversalInfo {
path: "/test/path".to_string(),
has_intent_engine: true,
is_selected: true,
};
let cloned = info.clone();
assert_eq!(cloned.path, info.path);
assert_eq!(cloned.has_intent_engine, info.has_intent_engine);
assert_eq!(cloned.is_selected, info.is_selected);
}
#[test]
fn test_directory_traversal_info_debug() {
let info = DirectoryTraversalInfo {
path: "/test/path".to_string(),
has_intent_engine: false,
is_selected: true,
};
let debug_str = format!("{:?}", info);
assert!(debug_str.contains("DirectoryTraversalInfo"));
assert!(debug_str.contains("/test/path"));
}
#[test]
fn test_directory_traversal_info_serialization() {
let info = DirectoryTraversalInfo {
path: "/test/path".to_string(),
has_intent_engine: true,
is_selected: false,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("path"));
assert!(json.contains("has_intent_engine"));
assert!(json.contains("is_selected"));
assert!(json.contains("/test/path"));
}
#[test]
fn test_directory_traversal_info_deserialization() {
let json = r#"{"path":"/test/path","has_intent_engine":true,"is_selected":false}"#;
let info: DirectoryTraversalInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.path, "/test/path");
assert!(info.has_intent_engine);
assert!(!info.is_selected);
}
#[test]
fn test_database_path_info_creation() {
let info = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/db.db".to_string()),
resolution_method: Some("Test Method".to_string()),
};
assert_eq!(info.current_working_directory, "/test/cwd");
assert!(!info.env_var_set);
assert_eq!(info.env_var_path, None);
assert_eq!(info.home_directory, Some("/home/user".to_string()));
assert!(!info.home_has_intent_engine);
assert_eq!(info.final_database_path, Some("/test/db.db".to_string()));
assert_eq!(info.resolution_method, Some("Test Method".to_string()));
}
#[test]
fn test_database_path_info_with_env_var() {
let info = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: true,
env_var_path: Some("/env/path".to_string()),
env_var_valid: Some(true),
directories_checked: vec![],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/env/path/.intent-engine/project.db".to_string()),
resolution_method: Some("Environment Variable".to_string()),
};
assert!(info.env_var_set);
assert_eq!(info.env_var_path, Some("/env/path".to_string()));
assert_eq!(info.env_var_valid, Some(true));
assert_eq!(
info.resolution_method,
Some("Environment Variable".to_string())
);
}
#[test]
fn test_database_path_info_with_directories() {
let dirs = vec![
DirectoryTraversalInfo {
path: "/test/path1".to_string(),
has_intent_engine: false,
is_selected: false,
},
DirectoryTraversalInfo {
path: "/test/path2".to_string(),
has_intent_engine: true,
is_selected: true,
},
];
let info = DatabasePathInfo {
current_working_directory: "/test/path1".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: dirs.clone(),
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/path2/.intent-engine/project.db".to_string()),
resolution_method: Some("Upward Directory Traversal".to_string()),
};
assert_eq!(info.directories_checked.len(), 2);
assert!(!info.directories_checked[0].has_intent_engine);
assert!(info.directories_checked[1].has_intent_engine);
assert!(info.directories_checked[1].is_selected);
}
#[test]
fn test_database_path_info_debug() {
let info = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/db.db".to_string()),
resolution_method: Some("Test".to_string()),
};
let debug_str = format!("{:?}", info);
assert!(debug_str.contains("DatabasePathInfo"));
assert!(debug_str.contains("/test/cwd"));
}
#[test]
fn test_database_path_info_serialization() {
let info = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: true,
env_var_path: Some("/env/path".to_string()),
env_var_valid: Some(true),
directories_checked: vec![],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/db.db".to_string()),
resolution_method: Some("Test Method".to_string()),
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("current_working_directory"));
assert!(json.contains("env_var_set"));
assert!(json.contains("env_var_path"));
assert!(json.contains("final_database_path"));
assert!(json.contains("/test/cwd"));
assert!(json.contains("/env/path"));
}
#[test]
fn test_database_path_info_deserialization() {
let json = r#"{
"current_working_directory": "/test/cwd",
"env_var_set": true,
"env_var_path": "/env/path",
"env_var_valid": true,
"directories_checked": [],
"home_directory": "/home/user",
"home_has_intent_engine": false,
"final_database_path": "/test/db.db",
"resolution_method": "Test Method"
}"#;
let info: DatabasePathInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.current_working_directory, "/test/cwd");
assert!(info.env_var_set);
assert_eq!(info.env_var_path, Some("/env/path".to_string()));
assert_eq!(info.env_var_valid, Some(true));
assert_eq!(info.home_directory, Some("/home/user".to_string()));
assert_eq!(info.final_database_path, Some("/test/db.db".to_string()));
assert_eq!(info.resolution_method, Some("Test Method".to_string()));
}
#[test]
fn test_database_path_info_complete_structure() {
let dirs = vec![
DirectoryTraversalInfo {
path: "/home/user/project/src".to_string(),
has_intent_engine: false,
is_selected: false,
},
DirectoryTraversalInfo {
path: "/home/user/project".to_string(),
has_intent_engine: true,
is_selected: true,
},
DirectoryTraversalInfo {
path: "/home/user".to_string(),
has_intent_engine: false,
is_selected: false,
},
];
let info = DatabasePathInfo {
current_working_directory: "/home/user/project/src".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: dirs,
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/home/user/project/.intent-engine/project.db".to_string()),
resolution_method: Some("Upward Directory Traversal".to_string()),
};
assert_eq!(info.directories_checked.len(), 3);
assert_eq!(info.directories_checked[0].path, "/home/user/project/src");
assert_eq!(info.directories_checked[1].path, "/home/user/project");
assert_eq!(info.directories_checked[2].path, "/home/user");
assert!(!info.directories_checked[0].is_selected);
assert!(info.directories_checked[1].is_selected);
assert!(!info.directories_checked[2].is_selected);
assert!(!info.directories_checked[0].has_intent_engine);
assert!(info.directories_checked[1].has_intent_engine);
assert!(!info.directories_checked[2].has_intent_engine);
}
#[test]
fn test_get_database_path_info_structure() {
let info = ProjectContext::get_database_path_info();
assert!(!info.current_working_directory.is_empty());
let has_data = !info.directories_checked.is_empty()
|| info.home_directory.is_some()
|| info.env_var_set;
assert!(
has_data,
"get_database_path_info should return some directory information"
);
}
#[test]
fn test_get_database_path_info_checks_current_dir() {
let info = ProjectContext::get_database_path_info();
assert!(!info.current_working_directory.is_empty());
if !info.env_var_set || info.env_var_valid != Some(true) {
assert!(
!info.directories_checked.is_empty(),
"Should check at least the current directory"
);
}
}
#[test]
fn test_get_database_path_info_includes_cwd() {
let info = ProjectContext::get_database_path_info();
if !info.env_var_set || info.env_var_valid != Some(true) {
assert!(!info.directories_checked.is_empty());
let cwd = &info.current_working_directory;
let first_checked = &info.directories_checked[0].path;
assert!(
cwd.starts_with(first_checked) || first_checked.starts_with(cwd),
"First checked directory should be related to CWD"
);
}
}
#[test]
fn test_get_database_path_info_resolution_method() {
let info = ProjectContext::get_database_path_info();
if info.final_database_path.is_some() {
assert!(
info.resolution_method.is_some(),
"Resolution method should be set when database path is found"
);
let method = info.resolution_method.unwrap();
assert!(
method.contains("Environment Variable")
|| method.contains("Upward Directory Traversal")
|| method.contains("Home Directory"),
"Resolution method should be one of the known strategies"
);
}
}
#[test]
fn test_get_database_path_info_selected_directory() {
let info = ProjectContext::get_database_path_info();
if (!info.env_var_set || info.env_var_valid != Some(true))
&& !info.directories_checked.is_empty()
&& info.final_database_path.is_some()
{
let selected_count = info
.directories_checked
.iter()
.filter(|d| d.is_selected)
.count();
assert!(
selected_count <= 1,
"At most one directory should be marked as selected"
);
if let Some(selected) = info.directories_checked.iter().find(|d| d.is_selected) {
assert!(
selected.has_intent_engine,
"Selected directory should have .intent-engine"
);
}
}
}
#[test]
fn test_database_path_info_no_database_found() {
let info = DatabasePathInfo {
current_working_directory: "/test/path".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![
DirectoryTraversalInfo {
path: "/test/path".to_string(),
has_intent_engine: false,
is_selected: false,
},
DirectoryTraversalInfo {
path: "/test".to_string(),
has_intent_engine: false,
is_selected: false,
},
],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: None,
resolution_method: None,
};
assert!(info.final_database_path.is_none());
assert!(info.resolution_method.is_none());
assert_eq!(info.directories_checked.len(), 2);
assert!(!info.home_has_intent_engine);
}
#[test]
fn test_database_path_info_env_var_invalid() {
let info = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: true,
env_var_path: Some("/invalid/path".to_string()),
env_var_valid: Some(false),
directories_checked: vec![DirectoryTraversalInfo {
path: "/test/cwd".to_string(),
has_intent_engine: true,
is_selected: true,
}],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/cwd/.intent-engine/project.db".to_string()),
resolution_method: Some("Upward Directory Traversal".to_string()),
};
assert!(info.env_var_set);
assert_eq!(info.env_var_valid, Some(false));
assert!(info.final_database_path.is_some());
assert!(info.resolution_method.unwrap().contains("Upward Directory"));
}
#[test]
fn test_database_path_info_home_directory_used() {
let info = DatabasePathInfo {
current_working_directory: "/tmp/work".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![
DirectoryTraversalInfo {
path: "/tmp/work".to_string(),
has_intent_engine: false,
is_selected: false,
},
DirectoryTraversalInfo {
path: "/tmp".to_string(),
has_intent_engine: false,
is_selected: false,
},
],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: true,
final_database_path: Some("/home/user/.intent-engine/project.db".to_string()),
resolution_method: Some("Home Directory Fallback".to_string()),
};
assert!(info.home_has_intent_engine);
assert_eq!(
info.final_database_path,
Some("/home/user/.intent-engine/project.db".to_string())
);
assert_eq!(
info.resolution_method,
Some("Home Directory Fallback".to_string())
);
}
#[test]
fn test_database_path_info_full_roundtrip() {
let original = DatabasePathInfo {
current_working_directory: "/test/cwd".to_string(),
env_var_set: true,
env_var_path: Some("/env/path".to_string()),
env_var_valid: Some(false),
directories_checked: vec![
DirectoryTraversalInfo {
path: "/test/cwd".to_string(),
has_intent_engine: false,
is_selected: false,
},
DirectoryTraversalInfo {
path: "/test".to_string(),
has_intent_engine: true,
is_selected: true,
},
],
home_directory: Some("/home/user".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/test/.intent-engine/project.db".to_string()),
resolution_method: Some("Upward Directory Traversal".to_string()),
};
let json = serde_json::to_string(&original).unwrap();
let deserialized: DatabasePathInfo = serde_json::from_str(&json).unwrap();
assert_eq!(
deserialized.current_working_directory,
original.current_working_directory
);
assert_eq!(deserialized.env_var_set, original.env_var_set);
assert_eq!(deserialized.env_var_path, original.env_var_path);
assert_eq!(deserialized.env_var_valid, original.env_var_valid);
assert_eq!(
deserialized.directories_checked.len(),
original.directories_checked.len()
);
assert_eq!(deserialized.home_directory, original.home_directory);
assert_eq!(
deserialized.home_has_intent_engine,
original.home_has_intent_engine
);
assert_eq!(
deserialized.final_database_path,
original.final_database_path
);
assert_eq!(deserialized.resolution_method, original.resolution_method);
}
#[test]
fn test_directory_traversal_info_all_combinations() {
let combinations = [(false, false), (false, true), (true, false), (true, true)];
for (has_ie, is_sel) in combinations.iter() {
let info = DirectoryTraversalInfo {
path: format!("/test/path/{}_{}", has_ie, is_sel),
has_intent_engine: *has_ie,
is_selected: *is_sel,
};
assert_eq!(info.has_intent_engine, *has_ie);
assert_eq!(info.is_selected, *is_sel);
}
}
#[test]
fn test_directory_traversal_info_exact_serialization() {
let info = DirectoryTraversalInfo {
path: "/exact/path/with/special-chars_123".to_string(),
has_intent_engine: true,
is_selected: false,
};
let json = serde_json::to_string(&info).unwrap();
let deserialized: DirectoryTraversalInfo = serde_json::from_str(&json).unwrap();
assert_eq!(info.path, deserialized.path);
assert_eq!(info.has_intent_engine, deserialized.has_intent_engine);
assert_eq!(info.is_selected, deserialized.is_selected);
}
#[test]
fn test_database_path_info_all_none() {
let info = DatabasePathInfo {
current_working_directory: "/test".to_string(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![],
home_directory: None,
home_has_intent_engine: false,
final_database_path: None,
resolution_method: None,
};
assert!(!info.env_var_set);
assert!(info.env_var_path.is_none());
assert!(info.env_var_valid.is_none());
assert!(info.directories_checked.is_empty());
assert!(info.home_directory.is_none());
assert!(info.final_database_path.is_none());
assert!(info.resolution_method.is_none());
}
#[test]
fn test_database_path_info_all_some() {
let info = DatabasePathInfo {
current_working_directory: "/test".to_string(),
env_var_set: true,
env_var_path: Some("/env".to_string()),
env_var_valid: Some(true),
directories_checked: vec![DirectoryTraversalInfo {
path: "/test".to_string(),
has_intent_engine: true,
is_selected: true,
}],
home_directory: Some("/home".to_string()),
home_has_intent_engine: true,
final_database_path: Some("/test/.intent-engine/project.db".to_string()),
resolution_method: Some("Test Method".to_string()),
};
assert!(info.env_var_set);
assert!(info.env_var_path.is_some());
assert!(info.env_var_valid.is_some());
assert!(!info.directories_checked.is_empty());
assert!(info.home_directory.is_some());
assert!(info.final_database_path.is_some());
assert!(info.resolution_method.is_some());
}
#[test]
fn test_get_database_path_info_home_directory() {
let home_val = std::env::var("HOME");
let userprofile_val = std::env::var("USERPROFILE");
let has_home = home_val.is_ok();
let has_userprofile = userprofile_val.is_ok();
let info = ProjectContext::get_database_path_info();
if (has_home || has_userprofile) && !info.env_var_set && info.directories_checked.is_empty()
{
assert!(
info.home_directory.is_some(),
"HOME or USERPROFILE env var is set (HOME={:?}, USERPROFILE={:?}), but home_directory is None. Full info: {:?}",
home_val.as_ref().map(|s| s.as_str()),
userprofile_val.as_ref().map(|s| s.as_str()),
info
);
}
}
#[test]
fn test_get_database_path_info_no_panic() {
let info = ProjectContext::get_database_path_info();
assert!(!info.current_working_directory.is_empty());
if info.final_database_path.is_none() {
let has_diagnostic_info = !info.directories_checked.is_empty()
|| info.env_var_set
|| info.home_directory.is_some();
assert!(
has_diagnostic_info,
"Even without finding a database, should provide diagnostic information"
);
}
}
#[test]
fn test_get_database_path_info_prefers_first_match() {
let info = ProjectContext::get_database_path_info();
if info
.resolution_method
.as_ref()
.is_some_and(|m| m.contains("Upward Directory"))
&& info.directories_checked.len() > 1
{
let with_ie: Vec<_> = info
.directories_checked
.iter()
.filter(|d| d.has_intent_engine)
.collect();
if with_ie.len() > 1 {
let selected: Vec<_> = with_ie.iter().filter(|d| d.is_selected).collect();
assert!(
selected.len() <= 1,
"Only the first .intent-engine found should be selected"
);
}
}
}
#[test]
fn test_database_path_info_partial_deserialization() {
let json = r#"{
"current_working_directory": "/test",
"env_var_set": false,
"env_var_path": null,
"env_var_valid": null,
"directories_checked": [],
"home_directory": null,
"home_has_intent_engine": false,
"final_database_path": null,
"resolution_method": null
}"#;
let info: DatabasePathInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.current_working_directory, "/test");
assert!(!info.env_var_set);
}
#[test]
fn test_database_path_info_json_schema() {
let info = DatabasePathInfo {
current_working_directory: "/test".to_string(),
env_var_set: true,
env_var_path: Some("/env".to_string()),
env_var_valid: Some(true),
directories_checked: vec![],
home_directory: Some("/home".to_string()),
home_has_intent_engine: false,
final_database_path: Some("/db".to_string()),
resolution_method: Some("Test".to_string()),
};
let json_value: serde_json::Value = serde_json::to_value(&info).unwrap();
assert!(json_value.get("current_working_directory").is_some());
assert!(json_value.get("env_var_set").is_some());
assert!(json_value.get("env_var_path").is_some());
assert!(json_value.get("env_var_valid").is_some());
assert!(json_value.get("directories_checked").is_some());
assert!(json_value.get("home_directory").is_some());
assert!(json_value.get("home_has_intent_engine").is_some());
assert!(json_value.get("final_database_path").is_some());
assert!(json_value.get("resolution_method").is_some());
}
#[test]
fn test_directory_traversal_info_empty_path() {
let info = DirectoryTraversalInfo {
path: "".to_string(),
has_intent_engine: false,
is_selected: false,
};
assert_eq!(info.path, "");
let json = serde_json::to_string(&info).unwrap();
let deserialized: DirectoryTraversalInfo = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.path, "");
}
#[test]
fn test_directory_traversal_info_unicode_path() {
let info = DirectoryTraversalInfo {
path: "/test/路径/مسار/путь".to_string(),
has_intent_engine: true,
is_selected: false,
};
let json = serde_json::to_string(&info).unwrap();
let deserialized: DirectoryTraversalInfo = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.path, "/test/路径/مسار/путь");
}
#[test]
fn test_database_path_info_long_paths() {
let long_path = "/".to_owned() + &"very_long_directory_name/".repeat(50);
let info = DatabasePathInfo {
current_working_directory: long_path.clone(),
env_var_set: false,
env_var_path: None,
env_var_valid: None,
directories_checked: vec![],
home_directory: Some(long_path.clone()),
home_has_intent_engine: false,
final_database_path: Some(long_path.clone()),
resolution_method: Some("Test".to_string()),
};
let json = serde_json::to_string(&info).unwrap();
let deserialized: DatabasePathInfo = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.current_working_directory, long_path);
}
#[test]
fn test_get_database_path_info_env_var_detection() {
let info = ProjectContext::get_database_path_info();
if std::env::var("INTENT_ENGINE_PROJECT_DIR").is_ok() {
assert!(
info.env_var_set,
"env_var_set should be true when INTENT_ENGINE_PROJECT_DIR is set"
);
assert!(
info.env_var_path.is_some(),
"env_var_path should contain the path when env var is set"
);
assert!(
info.env_var_valid.is_some(),
"env_var_valid should be set when env var is present"
);
} else {
assert!(
!info.env_var_set,
"env_var_set should be false when INTENT_ENGINE_PROJECT_DIR is not set"
);
assert!(
info.env_var_path.is_none(),
"env_var_path should be None when env var is not set"
);
assert!(
info.env_var_valid.is_none(),
"env_var_valid should be None when env var is not set"
);
}
}
}