use anyhow::Result;
use fsvalidator::model::{DirNode, FileNode, NodeName};
use std::fs;
#[test]
fn test_literal_file_validation_exists() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let file_path = test_dir.path().join("test_file.txt");
fs::write(&file_path, "test content")?;
let file_node = FileNode::new(NodeName::Literal("test_file.txt".to_string()), true);
assert!(file_node.validate(&file_path).is_ok());
Ok(())
}
#[test]
fn test_literal_file_validation_missing_required() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let missing_file_path = test_dir.path().join("test_file.txt");
let file_node = FileNode::new(NodeName::Literal("test_file.txt".to_string()), true);
let result = file_node.validate(&missing_file_path);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(validation_error.message.contains("Missing required file"));
assert!(matches!(validation_error.category, fsvalidator::ErrorCategory::Missing));
}
Ok(())
}
#[test]
fn test_literal_file_validation_missing_optional() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let missing_file_path = test_dir.path().join("test_file.txt");
let file_node = FileNode::new(NodeName::Literal("test_file.txt".to_string()), false);
assert!(file_node.validate(&missing_file_path).is_ok());
Ok(())
}
#[test]
fn test_pattern_file_validation() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let file_path = test_dir.path().join("test_file.txt");
fs::write(&file_path, "test content")?;
let file_node = FileNode::new(NodeName::Pattern("test_.*\\.txt".to_string()), true);
assert!(file_node.validate(&file_path).is_ok());
Ok(())
}
#[test]
fn test_pattern_file_validation_no_match() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let file_path = test_dir.path().join("wrong_file.txt");
fs::write(&file_path, "test content")?;
let file_node = FileNode::new(NodeName::Pattern("test_.*\\.txt".to_string()), true);
let result = file_node.validate(&file_path);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(validation_error.message.contains("doesn't match expected pattern"));
assert!(matches!(validation_error.category, fsvalidator::ErrorCategory::NameMismatch));
}
Ok(())
}
#[test]
fn test_literal_dir_validation() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("sub_dir");
fs::create_dir(&sub_dir)?;
let dir_node = DirNode::new(
NodeName::Literal("sub_dir".to_string()),
vec![],
true,
false,
vec![],
);
assert!(dir_node.validate(&sub_dir).is_ok());
Ok(())
}
#[test]
fn test_pattern_dir_validation() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("test_dir_123");
fs::create_dir(&sub_dir)?;
let dir_node = DirNode::new(
NodeName::Pattern("test_dir_\\d+".to_string()),
vec![],
true,
false,
vec![],
);
assert!(dir_node.validate(&sub_dir).is_ok());
Ok(())
}
#[test]
fn test_dir_with_children() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("sub_dir");
fs::create_dir(&sub_dir)?;
let file_in_sub_dir = sub_dir.join("test_file.txt");
fs::write(&file_in_sub_dir, "test content")?;
let file_node = FileNode::new(NodeName::Literal("test_file.txt".to_string()), true);
let dir_node = DirNode::new(
NodeName::Literal("sub_dir".to_string()),
vec![file_node],
true,
false,
vec![],
);
assert!(dir_node.validate(&sub_dir).is_ok());
Ok(())
}
#[test]
fn test_dir_with_missing_child() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("sub_dir");
fs::create_dir(&sub_dir)?;
let file_node = FileNode::new(NodeName::Literal("test_file.txt".to_string()), true);
let dir_node = DirNode::new(
NodeName::Literal("sub_dir".to_string()),
vec![file_node],
true,
false,
vec![],
);
let result = dir_node.validate(&sub_dir);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(!validation_error.children.is_empty());
assert!(validation_error.to_string().contains("test_file.txt"));
assert!(matches!(validation_error.category, fsvalidator::ErrorCategory::Other));
if !validation_error.children.is_empty() {
assert!(matches!(validation_error.children[0].category, fsvalidator::ErrorCategory::Missing));
}
}
Ok(())
}
#[test]
fn test_allow_defined_only() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("sub_dir");
fs::create_dir(&sub_dir)?;
let expected_file = sub_dir.join("expected.txt");
fs::write(&expected_file, "test content")?;
let unexpected_file = sub_dir.join("unexpected.txt");
fs::write(&unexpected_file, "test content")?;
let file_node = FileNode::new(NodeName::Literal("expected.txt".to_string()), true);
let dir_node = DirNode::new(
NodeName::Literal("sub_dir".to_string()),
vec![file_node],
true,
true, vec![],
);
let result = dir_node.validate(&sub_dir);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(validation_error.to_string().contains("Unexpected entry"));
let has_unexpected = validation_error.children.iter()
.any(|err| matches!(err.category, fsvalidator::ErrorCategory::Unexpected));
assert!(has_unexpected);
}
Ok(())
}
#[test]
fn test_mixed_pattern_and_literal() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir1 = test_dir.path().join("config");
fs::create_dir(&sub_dir1)?;
let sub_dir2 = test_dir.path().join("config_backup");
fs::create_dir(&sub_dir2)?;
fs::write(sub_dir1.join("settings.json"), "{}")?;
fs::write(sub_dir2.join("settings.json"), "{}")?;
let config_dir_pattern = NodeName::Pattern("config.*".to_string());
let config_dir1 = DirNode::new(
config_dir_pattern.clone(),
vec![FileNode::new(
NodeName::Literal("settings.json".to_string()),
true,
)],
true,
true,
vec![],
);
assert!(config_dir1.validate(&sub_dir1).is_ok());
let config_dir2 = DirNode::new(
config_dir_pattern,
vec![FileNode::new(
NodeName::Literal("settings.json".to_string()),
true,
)],
true,
true,
vec![],
);
assert!(config_dir2.validate(&sub_dir2).is_ok());
Ok(())
}
#[cfg(feature = "toml")]
#[test]
fn test_from_toml() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let toml_path = test_dir.path().join("test_config.toml");
let toml_content = r#"[root]
type = "dir"
name = "test_dir"
required = true
[[root.children]]
type = "file"
name = "test_file.txt"
required = true
[template]
# Empty but required
"#;
fs::write(&toml_path, toml_content)?;
let target_dir = test_dir.path().join("test_dir");
fs::create_dir(&target_dir)?;
fs::write(target_dir.join("test_file.txt"), "content")?;
let node = fsvalidator::from_toml(&toml_path)?;
assert!(node.validate(&target_dir).is_ok());
Ok(())
}
#[cfg(feature = "json")]
#[test]
fn test_from_json() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let json_path = test_dir.path().join("test_config.json");
let json_content = r#"{
"root": {
"type": "dir",
"name": "test_dir",
"required": true,
"children": [
{
"type": "file",
"name": "test_file.txt",
"required": true
}
]
},
"template": {}
}
"#;
fs::write(&json_path, json_content)?;
let target_dir = test_dir.path().join("test_dir");
fs::create_dir(&target_dir)?;
fs::write(target_dir.join("test_file.txt"), "content")?;
let node = fsvalidator::from_json(&json_path)?;
assert!(node.validate(&target_dir).is_ok());
Ok(())
}
#[test]
fn test_invalid_path_type() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let file_path = test_dir.path().join("not_a_dir");
fs::write(&file_path, "test content")?;
let dir_node = DirNode::new(
NodeName::Literal("not_a_dir".to_string()),
vec![],
true,
false,
vec![],
);
let result = dir_node.validate(&file_path);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not a directory"));
Ok(())
}
#[test]
fn test_dir_path_is_file() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let file_path = test_dir.path().join("file_not_dir");
fs::write(&file_path, "test content")?;
let child_file_node = FileNode::new(NodeName::Literal("some_child.txt".to_string()), true);
let dir_node = DirNode::new(
NodeName::Literal("file_not_dir".to_string()),
vec![child_file_node],
true,
false,
vec![],
);
let result = dir_node.validate(&file_path);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(validation_error.message.contains("not a directory"));
assert!(matches!(validation_error.category, fsvalidator::ErrorCategory::WrongType));
}
Ok(())
}
#[test]
fn test_multiple_validation_errors() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let sub_dir = test_dir.path().join("test_dir");
fs::create_dir(&sub_dir)?;
fs::write(sub_dir.join("wrong.txt"), "content")?;
let dir_node = DirNode::new(
NodeName::Literal("test_dir".to_string()),
vec![
FileNode::new(NodeName::Literal("required1.txt".to_string()), true),
FileNode::new(NodeName::Literal("required2.txt".to_string()), true),
],
true,
true, vec![],
);
let result = dir_node.validate(&sub_dir);
assert!(result.is_err());
if let Err(validation_error) = result {
assert!(validation_error.children.len() >= 3,
"Expected at least 3 errors, got {}", validation_error.children.len());
let error_string = validation_error.to_string();
assert!(error_string.contains("required1.txt"), "Missing error for required1.txt");
assert!(error_string.contains("required2.txt"), "Missing error for required2.txt");
assert!(error_string.contains("Unexpected entry"), "Missing error for unexpected file");
let has_missing = validation_error.children.iter()
.any(|err| matches!(err.category, fsvalidator::ErrorCategory::Missing));
let has_unexpected = validation_error.children.iter()
.any(|err| matches!(err.category, fsvalidator::ErrorCategory::Unexpected));
assert!(has_missing, "Missing errors should have Missing category");
assert!(has_unexpected, "Unexpected entry errors should have Unexpected category");
}
Ok(())
}