#![allow(deprecated)]
#![allow(dead_code)]
use std::fs;
use tempfile::TempDir;
use tree_type::tree_type;
tree_type! {
TestRoot {
#[required]
#[default(default_config)]
config("config.toml"),
#[required]
data/ {
#[optional]
logs/
}
}
}
fn default_config(_: &TestRootConfig) -> Result<String, std::io::Error> {
Ok("# Default configuration\nversion = 1".to_string())
}
tree_type! {
ProjectWithDefaults {
src/,
#[default(default_readme)]
readme("README.md"),
#[default(default_config_file)]
config("config.toml"),
}
}
fn default_readme(file: &ProjectWithDefaultsReadme) -> Result<String, std::io::Error> {
Ok(format!(
"# Project at {}\n\nDefault readme content\n",
file.as_path().display()
))
}
fn default_config_file(_file: &ProjectWithDefaultsConfig) -> Result<String, std::io::Error> {
Ok("# Config\ndefault = true\n".to_string())
}
tree_type! {
ProjectWithErrors {
src/,
#[default(failing_default)]
failing_file("fail.txt"),
#[default(working_default)]
working_file("work.txt"),
}
}
fn failing_default(_file: &ProjectWithErrorsFailingFile) -> Result<String, std::io::Error> {
Err(std::io::Error::other("intentional error"))
}
fn working_default(_file: &ProjectWithErrorsWorkingFile) -> Result<String, std::io::Error> {
Ok("working content\n".to_string())
}
tree_type! {
DynamicProject {
[id: String]/ as ProjectDir {
#[required]
src/,
#[default(project_config)]
config("config.toml")
}
}
}
fn project_config(file: &ProjectDirConfig) -> Result<String, std::io::Error> {
Ok(format!(
"# Config for project\npath = \"{}\"\n",
file.as_path().display()
))
}
tree_type! {
ComplexAttributes {
#[required]
required_dir/,
#[optional]
optional_dir/,
#[required]
#[default(complex_default)]
required_with_default("file.txt"),
#[optional]
#[default(optional_default)]
optional_with_default("optional.txt")
}
}
fn complex_default(_: &ComplexAttributesRequiredWithDefault) -> Result<String, std::io::Error> {
Ok("required default content".to_string())
}
fn optional_default(_: &ComplexAttributesOptionalWithDefault) -> Result<String, std::io::Error> {
Ok("optional default content".to_string())
}
#[test]
fn test_sync_method_exists_and_works() {
let temp_dir = TempDir::new().unwrap();
let root = TestRoot::new(temp_dir.path()).unwrap();
let result = root.sync();
assert!(result.is_ok(), "sync() should succeed: {:?}", result);
assert!(root.exists(), "Root directory should exist");
assert!(root.config().exists(), "Config file should be created");
assert!(root.data().exists(), "Data directory should be created");
let config_content = std::fs::read_to_string(root.config().as_path()).unwrap();
assert!(config_content.contains("Default configuration"));
}
#[test]
fn test_deprecated_methods_still_work() {
let temp_dir = TempDir::new().unwrap();
let root = TestRoot::new(temp_dir.path()).unwrap();
let sync_result_1 = root.sync();
assert!(sync_result_1.is_ok(), "sync() should work");
let sync_result = root.sync();
assert!(sync_result.is_ok(), "sync() should work");
}
#[test]
fn test_sync_creates_files_with_defaults() {
let temp_dir = TempDir::new().unwrap();
let project = ProjectWithDefaults::new(temp_dir.path()).unwrap();
let result = project.sync();
assert!(result.is_ok(), "sync() should succeed: {:?}", result);
assert!(project.src().exists(), "src/ should be created");
assert!(project.readme().exists(), "README.md should be created");
assert!(project.config().exists(), "config.toml should be created");
let readme_content = fs::read_to_string(project.readme().as_path()).unwrap();
assert!(readme_content.starts_with("# Project at"));
let config_content = fs::read_to_string(project.config().as_path()).unwrap();
assert_eq!(config_content, "# Config\ndefault = true\n");
}
#[test]
fn test_sync_skips_existing_files() {
let temp_dir = TempDir::new().unwrap();
let project = ProjectWithDefaults::new(temp_dir.path()).unwrap();
fs::create_dir_all(temp_dir.path()).unwrap();
fs::write(project.readme().as_path(), "# Custom\n").unwrap();
let result = project.sync();
assert!(result.is_ok(), "sync() should succeed: {:?}", result);
let readme_content = fs::read_to_string(project.readme().as_path()).unwrap();
assert_eq!(readme_content, "# Custom\n");
assert!(project.config().exists(), "config.toml should be created");
}
#[test]
fn test_sync_idempotent() {
let temp_dir = TempDir::new().unwrap();
let project = ProjectWithDefaults::new(temp_dir.path()).unwrap();
let result1 = project.sync();
assert!(result1.is_ok(), "First sync() should succeed");
assert!(project.exists(), "Project should exist after first sync");
let result2 = project.sync();
assert!(result2.is_ok(), "Second sync() should succeed");
assert!(
project.exists(),
"Project should still exist after second sync"
);
}
#[test]
fn test_sync_collects_errors() {
let temp_dir = TempDir::new().unwrap();
let project = ProjectWithErrors::new(temp_dir.path()).unwrap();
let result = project.sync();
assert!(
result.is_err(),
"sync() should fail due to error in default function"
);
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1, "Should have exactly one error");
assert!(
project.src().exists(),
"src/ should be created despite error"
);
assert!(
project.working_file().exists(),
"working_file should be created"
);
assert!(
!project.failing_file().exists(),
"failing_file should not be created"
);
}
#[test]
fn test_sync_with_dynamic_ids() {
let temp_dir = TempDir::new().unwrap();
let projects = DynamicProject::new(temp_dir.path()).unwrap();
let project1 = projects.id("project1".to_string());
let project2 = projects.id("project2".to_string());
project1.create().unwrap();
project2.create().unwrap();
let result1 = project1.sync();
assert!(result1.is_ok(), "sync() should work on dynamic ID instance");
assert!(project1.src().exists(), "project1 src/ should be created");
assert!(
project1.config().exists(),
"project1 config should be created"
);
let result2 = project2.sync();
assert!(
result2.is_ok(),
"sync() should work on second dynamic ID instance"
);
assert!(project2.src().exists(), "project2 src/ should be created");
assert!(
project2.config().exists(),
"project2 config should be created"
);
}
#[test]
fn test_sync_complex_attribute_combinations() {
let temp_dir = TempDir::new().unwrap();
let complex = ComplexAttributes::new(temp_dir.path()).unwrap();
let result = complex.sync();
assert!(
result.is_ok(),
"sync() should handle complex attributes: {:?}",
result
);
assert!(
complex.required_dir().exists(),
"required_dir/ should be created"
);
assert!(
complex.required_with_default().exists(),
"required_with_default should be created"
);
assert!(
complex.optional_with_default().exists(),
"optional_with_default should be created"
);
let required_content = fs::read_to_string(complex.required_with_default().as_path()).unwrap();
assert_eq!(required_content, "required default content");
let optional_content = fs::read_to_string(complex.optional_with_default().as_path()).unwrap();
assert_eq!(optional_content, "optional default content");
}
#[test]
fn test_sync_validation_integration() {
let temp_dir = TempDir::new().unwrap();
let root = TestRoot::new(temp_dir.path()).unwrap();
let result = root.sync();
assert!(result.is_ok(), "sync() should succeed");
}
#[test]
fn test_sync_recreates_missing_required_items() {
let temp_dir = TempDir::new().unwrap();
let root = TestRoot::new(temp_dir.path()).unwrap();
root.sync().unwrap();
assert!(root.data().exists(), "data/ should exist after sync");
fs::remove_dir_all(root.data().as_path()).unwrap();
assert!(!root.data().exists(), "data/ should be removed");
let result = root.sync();
assert!(
result.is_ok(),
"sync() should recreate missing required items"
);
assert!(root.data().exists(), "data/ should be recreated by sync");
}
#[test]
fn test_sync_nested_structures() {
let temp_dir = TempDir::new().unwrap();
let root = TestRoot::new(temp_dir.path()).unwrap();
let result = root.sync();
assert!(result.is_ok(), "sync() should handle nested structures");
assert!(root.exists(), "Root should exist");
assert!(root.data().exists(), "data/ should exist");
let _logs_exists = root.data().logs().exists();
}