zeroclaw/src/skills/symlink_tests.rs
2026-02-19 14:52:29 +08:00

116 lines
4.6 KiB
Rust

#[cfg(test)]
mod tests {
use crate::skills::skills_dir;
use std::path::Path;
use tempfile::TempDir;
#[tokio::test]
async fn test_skills_symlink_unix_edge_cases() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
tokio::fs::create_dir_all(&workspace_dir).await.unwrap();
let skills_path = skills_dir(&workspace_dir);
tokio::fs::create_dir_all(&skills_path).await.unwrap();
// Test case 1: Valid symlink creation on Unix
#[cfg(unix)]
{
let source_dir = tmp.path().join("source_skill");
tokio::fs::create_dir_all(&source_dir).await.unwrap();
tokio::fs::write(source_dir.join("SKILL.md"), "# Test Skill\nContent")
.await
.unwrap();
let dest_link = skills_path.join("linked_skill");
// Create symlink
let result = std::os::unix::fs::symlink(&source_dir, &dest_link);
assert!(result.is_ok(), "Symlink creation should succeed");
// Verify symlink works
assert!(dest_link.exists());
assert!(dest_link.is_symlink());
// Verify we can read through symlink
let content = tokio::fs::read_to_string(dest_link.join("SKILL.md")).await;
assert!(content.is_ok());
assert!(content.unwrap().contains("Test Skill"));
// Test case 2: Symlink to non-existent target should fail gracefully
let broken_link = skills_path.join("broken_skill");
let non_existent = tmp.path().join("non_existent");
let result = std::os::unix::fs::symlink(&non_existent, &broken_link);
assert!(
result.is_ok(),
"Symlink creation should succeed even if target doesn't exist"
);
// But reading through it should fail
let content = tokio::fs::read_to_string(broken_link.join("SKILL.md")).await;
assert!(content.is_err());
}
// Test case 3: Non-Unix platforms should handle symlink errors gracefully
#[cfg(windows)]
{
let source_dir = tmp.path().join("source_skill");
tokio::fs::create_dir_all(&source_dir).await.unwrap();
let dest_link = skills_path.join("linked_skill");
// On Windows, creating directory symlinks may require elevated privileges
let result = std::os::windows::fs::symlink_dir(&source_dir, &dest_link);
// If symlink creation fails (no privileges), the directory should not exist
if result.is_err() {
assert!(!dest_link.exists());
} else {
// Clean up if it succeeded
let _ = tokio::fs::remove_dir(&dest_link).await;
}
}
// Test case 4: skills_dir function edge cases
let workspace_with_trailing_slash = format!("{}/", workspace_dir.display());
let path_from_str = skills_dir(Path::new(&workspace_with_trailing_slash));
assert_eq!(path_from_str, skills_path);
// Test case 5: Empty workspace directory
let empty_workspace = tmp.path().join("empty");
let empty_skills_path = skills_dir(&empty_workspace);
assert_eq!(empty_skills_path, empty_workspace.join("skills"));
assert!(!empty_skills_path.exists());
}
#[tokio::test]
async fn test_skills_symlink_permissions_and_safety() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
tokio::fs::create_dir_all(&workspace_dir).await.unwrap();
let skills_path = skills_dir(&workspace_dir);
tokio::fs::create_dir_all(&skills_path).await.unwrap();
#[cfg(unix)]
{
// Test case: Symlink outside workspace should be allowed (user responsibility)
let outside_dir = tmp.path().join("outside_skill");
tokio::fs::create_dir_all(&outside_dir).await.unwrap();
tokio::fs::write(outside_dir.join("SKILL.md"), "# Outside Skill\nContent")
.await
.unwrap();
let dest_link = skills_path.join("outside_skill");
let result = std::os::unix::fs::symlink(&outside_dir, &dest_link);
assert!(
result.is_ok(),
"Should allow symlinking to directories outside workspace"
);
// Should still be readable
let content = tokio::fs::read_to_string(dest_link.join("SKILL.md")).await;
assert!(content.is_ok());
assert!(content.unwrap().contains("Outside Skill"));
}
}
}