#[cfg(test)]
mod tests {
use crate::core::error::TookaError;
use crate::core::sorter::{MatchResult, collect_files, sort_files};
use crate::rules::rule::{Action, Conditions, CopyAction, MoveAction, Rule};
use crate::rules::rules_file::RulesFile;
use crate::utils::gen_pdf::generate_pdf;
use std::fs::{File, create_dir_all};
use std::io::Write;
use tempfile::tempdir;
fn create_test_file(path: &std::path::Path, content: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
fn create_test_files(base_dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut files = Vec::new();
let test_data = [
("test1.txt", "text content"),
("test2.log", "log content"),
("test3.data", "data content"),
("test4.bin", "binary content"),
("test5.unknown", "unknown content"),
];
for (filename, content) in test_data {
let file_path = base_dir.join(filename);
create_test_file(&file_path, content).unwrap();
files.push(file_path);
}
files
}
fn create_test_rules(dest_dir: &std::path::Path) -> RulesFile {
let txt_dir = dest_dir.join("txt_files");
let log_dir = dest_dir.join("log_files");
let data_dir = dest_dir.join("data_files");
create_dir_all(&txt_dir).unwrap();
create_dir_all(&log_dir).unwrap();
create_dir_all(&data_dir).unwrap();
let rules = vec![
Rule {
id: "txt_rule".to_string(),
name: "Move txt files".to_string(),
enabled: true,
description: Some("Move all .txt files to txt_files directory".to_string()),
priority: 1,
when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: Some(vec!["txt".to_string()]),
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: txt_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
Rule {
id: "log_rule".to_string(),
name: "Copy log files".to_string(),
enabled: true,
description: Some("Copy all .log files to log_files directory".to_string()),
priority: 2,
when: Conditions {
any: Some(false),
filename: Some(r".*\.log$".to_string()),
extensions: Some(vec!["log".to_string()]),
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Copy(CopyAction {
to: log_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
Rule {
id: "data_rule".to_string(),
name: "Move data files".to_string(),
enabled: true,
description: Some("Move all .data files to data_files directory".to_string()),
priority: 3,
when: Conditions {
any: Some(false),
filename: Some(r".*\.data$".to_string()),
extensions: Some(vec!["data".to_string()]),
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: data_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
];
RulesFile { rules }
}
#[test]
fn test_sort_files_basic() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let files = create_test_files(&source_path);
let rules_file = create_test_rules(&source_path);
let results = sort_files(&files, &source_path, &rules_file, true, None::<fn()>)
.expect("sort_files should succeed");
assert_eq!(results.len(), files.len());
let txt_result = results.iter().find(|r| r.file_name == "test1.txt").unwrap();
assert_eq!(txt_result.matched_rule_id, "txt_rule");
assert_eq!(txt_result.action, "move");
let log_result = results.iter().find(|r| r.file_name == "test2.log").unwrap();
assert_eq!(log_result.matched_rule_id, "log_rule");
assert_eq!(log_result.action, "copy");
let data_result = results
.iter()
.find(|r| r.file_name == "test3.data")
.unwrap();
assert_eq!(data_result.matched_rule_id, "data_rule");
assert_eq!(data_result.action, "move");
let unknown_result = results
.iter()
.find(|r| r.file_name == "test5.unknown")
.unwrap();
assert_eq!(unknown_result.matched_rule_id, "none");
assert_eq!(unknown_result.action, "skip");
}
#[test]
fn test_sort_files_actual_execution() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let files = create_test_files(&source_path);
let rules_file = create_test_rules(&source_path);
let results = sort_files(&files, &source_path, &rules_file, false, None::<fn()>)
.expect("sort_files should succeed");
let txt_result = results.iter().find(|r| r.file_name == "test1.txt").unwrap();
assert!(
txt_result.new_path.exists(),
"txt file should be moved to new location"
);
assert!(
!source_path.join("test1.txt").exists(),
"txt file should be moved from original location"
);
let log_result = results.iter().find(|r| r.file_name == "test2.log").unwrap();
assert!(
log_result.new_path.exists(),
"log file should be copied to new location"
);
assert!(
source_path.join("test2.log").exists(),
"log file should still exist in original location"
);
let data_result = results
.iter()
.find(|r| r.file_name == "test3.data")
.unwrap();
assert!(
data_result.new_path.exists(),
"data file should be moved to new location"
);
assert!(
!source_path.join("test3.data").exists(),
"data file should be moved from original location"
);
}
#[test]
fn test_sort_files_with_priority() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let test_file = source_path.join("test.txt");
create_test_file(&test_file, "test content").unwrap();
let high_priority_dir = source_path.join("high_priority");
let low_priority_dir = source_path.join("low_priority");
create_dir_all(&high_priority_dir).unwrap();
create_dir_all(&low_priority_dir).unwrap();
let rules = vec![
Rule {
id: "low_priority_rule".to_string(),
name: "Low priority rule".to_string(),
enabled: true,
description: None,
priority: 1, when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: low_priority_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
Rule {
id: "high_priority_rule".to_string(),
name: "High priority rule".to_string(),
enabled: true,
description: None,
priority: 10, when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: high_priority_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
];
let rules_file = RulesFile { rules };
let optimized_rules = rules_file.optimized_with_filter(None).unwrap();
let results = sort_files(
&[test_file],
&source_path,
&optimized_rules,
true,
None::<fn()>,
)
.expect("sort_files should succeed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].matched_rule_id, "high_priority_rule");
}
#[test]
fn test_sort_files_with_progress_callback() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let files = create_test_files(&source_path);
let rules_file = create_test_rules(&source_path);
let progress_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let progress_count_clone = progress_count.clone();
let progress_callback = move || {
progress_count_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
};
let results = sort_files(
&files,
&source_path,
&rules_file,
true,
Some(progress_callback),
)
.expect("sort_files should succeed");
assert_eq!(
progress_count.load(std::sync::atomic::Ordering::SeqCst),
files.len()
);
assert_eq!(results.len(), files.len());
}
#[test]
fn test_sort_files_multiple_actions() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let test_file = source_path.join("test.txt");
create_test_file(&test_file, "test content").unwrap();
let copy_dir = source_path.join("copy_dest");
let move_dir = source_path.join("move_dest");
create_dir_all(©_dir).unwrap();
create_dir_all(&move_dir).unwrap();
let rules = vec![Rule {
id: "multi_action_rule".to_string(),
name: "Multi action rule".to_string(),
enabled: true,
description: None,
priority: 1,
when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![
Action::Copy(CopyAction {
to: copy_dir.to_string_lossy().to_string(),
preserve_structure: false,
}),
Action::Move(MoveAction {
to: move_dir.to_string_lossy().to_string(),
preserve_structure: false,
}),
],
}];
let rules_file = RulesFile { rules };
let results = sort_files(&[test_file], &source_path, &rules_file, true, None::<fn()>)
.expect("sort_files should succeed");
assert_eq!(results.len(), 2);
assert_eq!(results[0].action, "copy");
assert_eq!(results[1].action, "move");
assert_eq!(results[0].matched_rule_id, "multi_action_rule");
assert_eq!(results[1].matched_rule_id, "multi_action_rule");
}
#[test]
fn test_collect_files() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path();
let sub_dir = source_path.join("subdir");
create_dir_all(&sub_dir).unwrap();
let files = [
source_path.join("file1.txt"),
source_path.join("file2.log"),
sub_dir.join("file3.data"),
];
for file in &files {
create_test_file(file, "content").unwrap();
}
let collected = collect_files(source_path).expect("collect_files should succeed");
assert_eq!(collected.len(), 3);
for file in &files {
assert!(
collected.contains(file),
"Should find file: {}",
file.display()
);
}
}
#[test]
fn test_collect_files_nonexistent_directory() {
let temp_dir = tempdir().unwrap();
let nonexistent_path = temp_dir.path().join("nonexistent");
let result = collect_files(&nonexistent_path);
assert!(result.is_err());
if let Err(TookaError::ConfigError(msg)) = result {
assert!(msg.contains("does not exist"));
} else {
panic!("Expected ConfigError");
}
}
#[test]
fn test_sort_files_empty_file_list() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let rules_file = create_test_rules(&source_path);
let results = sort_files(&[], &source_path, &rules_file, true, None::<fn()>)
.expect("sort_files should succeed with empty list");
assert_eq!(results.len(), 0);
}
#[test]
fn test_sort_files_disabled_rules() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let test_file = source_path.join("test.txt");
create_test_file(&test_file, "test content").unwrap();
let rules = vec![Rule {
id: "disabled_rule".to_string(),
name: "Disabled rule".to_string(),
enabled: false, description: None,
priority: 1,
when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: source_path.join("dest").to_string_lossy().to_string(),
preserve_structure: false,
})],
}];
let rules_file = RulesFile { rules };
let result = rules_file.optimized_with_filter(None);
assert!(result.is_err());
if let Err(TookaError::RuleNotFound(msg)) = result {
assert!(msg.contains("No enabled rules found"));
} else {
panic!("Expected RuleNotFound error");
}
}
#[test]
fn test_sort_files_mixed_enabled_disabled_rules() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let test_file = source_path.join("test.txt");
create_test_file(&test_file, "test content").unwrap();
let enabled_dir = source_path.join("enabled_dest");
let disabled_dir = source_path.join("disabled_dest");
create_dir_all(&enabled_dir).unwrap();
create_dir_all(&disabled_dir).unwrap();
let rules = vec![
Rule {
id: "disabled_rule".to_string(),
name: "Disabled rule".to_string(),
enabled: false, description: None,
priority: 10, when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: disabled_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
Rule {
id: "enabled_rule".to_string(),
name: "Enabled rule".to_string(),
enabled: true, description: None,
priority: 5, when: Conditions {
any: Some(false),
filename: Some(r".*\.txt$".to_string()),
extensions: None,
path: None,
size_kb: None,
mime_type: None,
created_date: None,
modified_date: None,
is_symlink: None,
metadata: None,
},
then: vec![Action::Move(MoveAction {
to: enabled_dir.to_string_lossy().to_string(),
preserve_structure: false,
})],
},
];
let rules_file = RulesFile { rules };
let optimized_rules = rules_file.optimized_with_filter(None).unwrap();
let results = sort_files(
std::slice::from_ref(&test_file),
&source_path,
&optimized_rules,
true,
None::<fn()>,
)
.expect("sort_files should succeed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].matched_rule_id, "enabled_rule");
assert_eq!(results[0].action, "move");
assert!(
results[0]
.new_path
.to_string_lossy()
.contains("enabled_dest")
);
}
#[test]
fn test_pdf_generation() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let files = create_test_files(&source_path);
let rules_file = create_test_rules(&source_path);
let results = sort_files(
&files,
&source_path,
&rules_file,
true, None::<fn()>,
)
.expect("sort_files should succeed");
assert!(!results.is_empty(), "Should have some match results");
let pdf_path = temp_dir.path().join("test_report.pdf");
generate_pdf(&pdf_path, &results).expect("PDF generation should succeed");
assert!(pdf_path.exists(), "PDF file should be created");
let metadata = std::fs::metadata(&pdf_path).expect("Should be able to read PDF metadata");
assert!(
metadata.len() > 500,
"PDF should have reasonable size (> 500 bytes), actual size: {} bytes",
metadata.len()
);
println!("Generated PDF at: {}", pdf_path.display());
println!("PDF size: {} bytes", metadata.len());
println!("Match results count: {}", results.len());
let txt_result = results.iter().find(|r| r.file_name == "test1.txt");
let log_result = results.iter().find(|r| r.file_name == "test2.log");
let data_result = results.iter().find(|r| r.file_name == "test3.data");
let unknown_result = results.iter().find(|r| r.file_name == "test5.unknown");
assert!(txt_result.is_some(), "Should have txt result");
assert!(log_result.is_some(), "Should have log result");
assert!(data_result.is_some(), "Should have data result");
assert!(unknown_result.is_some(), "Should have unknown result");
if let Some(txt) = txt_result {
assert_eq!(txt.action, "move");
assert_eq!(txt.matched_rule_id, "txt_rule");
}
if let Some(log) = log_result {
assert_eq!(log.action, "copy");
assert_eq!(log.matched_rule_id, "log_rule");
}
if let Some(data) = data_result {
assert_eq!(data.action, "move");
assert_eq!(data.matched_rule_id, "data_rule");
}
if let Some(unknown) = unknown_result {
assert_eq!(unknown.action, "skip");
assert_eq!(unknown.matched_rule_id, "none");
}
}
#[test]
fn test_pdf_generation_with_large_dataset() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().to_path_buf();
let mut mock_results = Vec::new();
for i in 0..50 {
mock_results.push(MatchResult {
file_name: format!("file{i}.txt"),
current_path: source_path.join(format!("file{i}.txt")),
new_path: source_path.join("txt_files").join(format!("file{i}.txt")),
matched_rule_id: "txt_rule".to_string(),
action: "move".to_string(),
});
}
for i in 0..30 {
mock_results.push(MatchResult {
file_name: format!("log{i}.log"),
current_path: source_path.join(format!("log{i}.log")),
new_path: source_path.join("log_files").join(format!("log{i}.log")),
matched_rule_id: "log_rule".to_string(),
action: "copy".to_string(),
});
}
for i in 0..20 {
mock_results.push(MatchResult {
file_name: format!("data{i}.data"),
current_path: source_path.join(format!("data{i}.data")),
new_path: source_path.join("data_files").join(format!("data{i}.data")),
matched_rule_id: "data_rule".to_string(),
action: "move".to_string(),
});
}
for i in 0..10 {
mock_results.push(MatchResult {
file_name: format!("executable{i}.exe"),
current_path: source_path.join(format!("executable{i}.exe")),
new_path: source_path
.join("executed")
.join(format!("executed_{i}.exe")),
matched_rule_id: "execute_rule".to_string(),
action: "execute".to_string(),
});
}
for i in 0..15 {
mock_results.push(MatchResult {
file_name: format!("unknown{i}.unknown"),
current_path: source_path.join(format!("unknown{i}.unknown")),
new_path: source_path.join(format!("unknown{i}.unknown")), matched_rule_id: "none".to_string(),
action: "skip".to_string(),
});
}
let pdf_path = temp_dir.path().join("large_test_report.pdf");
generate_pdf(&pdf_path, &mock_results)
.expect("PDF generation should succeed with large dataset");
assert!(pdf_path.exists(), "Large PDF file should be created");
let metadata =
std::fs::metadata(&pdf_path).expect("Should be able to read large PDF metadata");
assert!(
metadata.len() > 5000,
"Large PDF should have substantial size (> 5KB), actual size: {} bytes",
metadata.len()
);
println!("Generated large PDF at: {}", pdf_path.display());
println!("Large PDF size: {} bytes", metadata.len());
println!("Total match results: {}", mock_results.len());
assert_eq!(mock_results.len(), 125);
let move_count = mock_results.iter().filter(|r| r.action == "move").count();
let copy_count = mock_results.iter().filter(|r| r.action == "copy").count();
let execute_count = mock_results
.iter()
.filter(|r| r.action == "execute")
.count();
let skip_count = mock_results.iter().filter(|r| r.action == "skip").count();
assert_eq!(move_count, 70); assert_eq!(copy_count, 30); assert_eq!(execute_count, 10); assert_eq!(skip_count, 15); }
#[test]
fn test_pdf_generation_for_inspection() {
let pdf_path = std::path::Path::new("test_report_refactored.pdf");
let mut mock_results = Vec::new();
let base_path = std::path::Path::new("/example/source");
for i in 0..15 {
mock_results.push(MatchResult {
file_name: format!("document_{i}.txt"),
current_path: base_path.join(format!("documents/document_{i}.txt")),
new_path: base_path
.join("organized/documents")
.join(format!("document_{i}.txt")),
matched_rule_id: "document_organization_rule".to_string(),
action: "move".to_string(),
});
}
for i in 0..12 {
mock_results.push(MatchResult {
file_name: format!("backup_{i}.log"),
current_path: base_path.join(format!("logs/backup_{i}.log")),
new_path: base_path
.join("archive/logs")
.join(format!("backup_{i}.log")),
matched_rule_id: "log_backup_rule".to_string(),
action: "copy".to_string(),
});
}
for i in 0..8 {
mock_results.push(MatchResult {
file_name: format!("temp_{i}.tmp"),
current_path: base_path.join(format!("temp/temp_{i}.tmp")),
new_path: base_path.join("temp").join(format!("temp_{i}.tmp")), matched_rule_id: "cleanup_rule".to_string(),
action: "delete".to_string(),
});
}
for i in 0..6 {
mock_results.push(MatchResult {
file_name: format!("old_file_{i}.dat"),
current_path: base_path.join(format!("data/old_file_{i}.dat")),
new_path: base_path.join("data").join(format!("new_file_{i}.dat")),
matched_rule_id: "rename_rule".to_string(),
action: "rename".to_string(),
});
}
for i in 0..5 {
mock_results.push(MatchResult {
file_name: format!("script_{i}.sh"),
current_path: base_path.join(format!("scripts/script_{i}.sh")),
new_path: base_path
.join("executed")
.join(format!("executed_script_{i}.result")),
matched_rule_id: "script_execution_rule".to_string(),
action: "execute".to_string(),
});
}
for i in 0..10 {
mock_results.push(MatchResult {
file_name: format!("unknown_{i}.xyz"),
current_path: base_path.join(format!("misc/unknown_{i}.xyz")),
new_path: base_path.join("misc").join(format!("unknown_{i}.xyz")), matched_rule_id: "none".to_string(),
action: "skip".to_string(),
});
}
generate_pdf(pdf_path, &mock_results).expect("PDF generation should succeed");
assert!(pdf_path.exists(), "PDF file should be created");
let metadata = std::fs::metadata(pdf_path).expect("Should be able to read PDF metadata");
println!("Generated PDF for inspection at: {}", pdf_path.display());
println!("PDF size: {} bytes", metadata.len());
println!("Total match results: {}", mock_results.len());
let move_count = mock_results.iter().filter(|r| r.action == "move").count();
let copy_count = mock_results.iter().filter(|r| r.action == "copy").count();
let delete_count = mock_results.iter().filter(|r| r.action == "delete").count();
let rename_count = mock_results.iter().filter(|r| r.action == "rename").count();
let execute_count = mock_results
.iter()
.filter(|r| r.action == "execute")
.count();
let skip_count = mock_results.iter().filter(|r| r.action == "skip").count();
println!("Action breakdown:");
println!(" Move: {move_count}");
println!(" Copy: {copy_count}");
println!(" Delete: {delete_count}");
println!(" Rename: {rename_count}");
println!(" Execute: {execute_count}");
println!(" Skip: {skip_count}");
assert!(
metadata.len() > 2000,
"PDF should be substantial for inspection"
);
}
#[test]
fn test_pdf_generation_with_long_paths() {
let pdf_path = std::path::Path::new("test_report_long_paths.pdf");
let mock_results = vec![
MatchResult {
file_name:
"document_with_very_very_very_long_filename_that_should_be_handled_properly.txt"
.to_string(),
current_path: std::path::PathBuf::from(
"/home/user/documents/projects/rust/my_awesome_project/src/very/deeply/nested/directories/with/extremely/long/path/names/that/go/on/and/on/document_with_very_very_very_long_filename_that_should_be_handled_properly.txt",
),
new_path: std::path::PathBuf::from(
"/home/user/organized_files/documents/text_files/2024/august/important_documents/document_with_very_very_very_long_filename_that_should_be_handled_properly.txt",
),
matched_rule_id: "document_organization_with_very_long_rule_name".to_string(),
action: "move".to_string(),
},
MatchResult {
file_name: "short.log".to_string(),
current_path: std::path::PathBuf::from(
"/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/very/long/path/structure/short.log",
),
new_path: std::path::PathBuf::from("/backup/logs/short.log"),
matched_rule_id: "log_backup".to_string(),
action: "copy".to_string(),
},
MatchResult {
file_name: "file_in_normal_path.dat".to_string(),
current_path: std::path::PathBuf::from("/home/user/data/file_in_normal_path.dat"),
new_path: std::path::PathBuf::from("/home/user/archived/file_in_normal_path.dat"),
matched_rule_id: "normal_rule".to_string(),
action: "move".to_string(),
},
];
generate_pdf(pdf_path, &mock_results).expect("PDF generation should succeed");
assert!(pdf_path.exists(), "PDF file should be created");
let metadata = std::fs::metadata(pdf_path).expect("Should be able to read PDF metadata");
println!("Generated PDF with long paths at: {}", pdf_path.display());
println!("PDF size: {} bytes", metadata.len());
println!("Total match results: {}", mock_results.len());
assert!(
metadata.len() > 1000,
"PDF should be substantial for inspection"
);
}
}