Files
noentropy/tests/test_offline_handler.rs
2026-01-10 00:05:26 +05:30

339 lines
10 KiB
Rust

//! Unit tests for handle_offline_organization handler
//!
//! Tests the offline file organization functionality including:
//! - Empty batch handling
//! - Unknown extension handling
//! - Dry run behavior
//! - Various file extension categorization
//! - Undo log behavior
//! - Helper function behavior
use noentropy::cli::handlers::handle_offline_organization;
use noentropy::files::FileBatch;
use noentropy::models::{FileCategory, OrganizationPlan};
use noentropy::storage::UndoLog;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use tempfile::TempDir;
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/// Helper to create a temporary directory with test files
fn setup_test_dir_with_files(files: &[&str]) -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let dir_path = temp_dir.path().to_path_buf();
for filename in files {
let file_path = dir_path.join(filename);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).unwrap();
}
File::create(&file_path).unwrap();
}
(temp_dir, dir_path)
}
/// Helper to create a FileBatch from a list of filenames and a base path
fn create_file_batch(filenames: Vec<String>, base_path: &Path) -> FileBatch {
let paths: Vec<PathBuf> = filenames.iter().map(|f| base_path.join(f)).collect();
FileBatch { filenames, paths }
}
// ============================================================================
// HANDLER TESTS
// ============================================================================
#[test]
fn test_handle_offline_organization_empty_batch() {
let temp_dir = TempDir::new().unwrap();
let target_path = temp_dir.path();
let mut undo_log = UndoLog::new();
let batch = FileBatch {
filenames: vec![],
paths: vec![],
};
let result = handle_offline_organization(batch, target_path, true, &mut undo_log);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_handle_offline_organization_all_unknown_extensions() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["file1.xyz", "file2.unknown"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec!["file1.xyz".to_string(), "file2.unknown".to_string()],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
// Should return None when no files can be categorized
assert!(result.unwrap().is_none());
}
#[test]
fn test_handle_offline_organization_dry_run_no_file_moves() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["photo.jpg", "document.pdf"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec!["photo.jpg".to_string(), "document.pdf".to_string()],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
// In dry run, files should NOT be moved
assert!(dir_path.join("photo.jpg").exists());
assert!(dir_path.join("document.pdf").exists());
// Destination folders should NOT be created
assert!(!dir_path.join("Images").exists());
assert!(!dir_path.join("Documents").exists());
}
#[test]
fn test_handle_offline_organization_mixed_files() {
let (_temp_dir, dir_path) =
setup_test_dir_with_files(&["photo.jpg", "document.pdf", "unknown.xyz", "song.mp3"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec![
"photo.jpg".to_string(),
"document.pdf".to_string(),
"unknown.xyz".to_string(),
"song.mp3".to_string(),
],
&dir_path,
);
// Dry run to verify categorization without moving
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
// Files should still exist (dry run)
assert!(dir_path.join("photo.jpg").exists());
assert!(dir_path.join("document.pdf").exists());
assert!(dir_path.join("unknown.xyz").exists());
assert!(dir_path.join("song.mp3").exists());
}
#[test]
fn test_handle_offline_organization_various_extensions() {
let files = vec![
// Images
"test.png",
"test.gif",
"test.webp",
// Documents
"test.docx",
"test.xlsx",
"test.txt",
// Code
"test.rs",
"test.py",
"test.js",
// Archives
"test.zip",
"test.tar",
// Video
"test.mp4",
"test.mkv",
// Music
"test.wav",
"test.flac",
// Installers
"test.exe",
"test.dmg",
];
let (_temp_dir, dir_path) = setup_test_dir_with_files(&files);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(files.iter().map(|s| s.to_string()).collect(), &dir_path);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}
#[test]
fn test_handle_offline_organization_case_insensitive() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["PHOTO.JPG", "Document.PDF"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec!["PHOTO.JPG".to_string(), "Document.PDF".to_string()],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}
#[test]
fn test_handle_offline_organization_undo_log_not_modified_in_dry_run() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["photo.jpg"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(vec!["photo.jpg".to_string()], &dir_path);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
// Undo log should be empty in dry run mode
assert_eq!(undo_log.get_completed_count(), 0);
}
#[test]
fn test_handle_offline_organization_files_without_extension() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["README", "Makefile", ".gitignore"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec![
"README".to_string(),
"Makefile".to_string(),
".gitignore".to_string(),
],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
// All files have no/unknown extensions, should return None
assert!(result.unwrap().is_none());
}
// ============================================================================
// ORGANIZATION PLAN TESTS
// ============================================================================
#[test]
fn test_organization_plan_structure() {
let plan = OrganizationPlan {
files: vec![
FileCategory {
filename: "photo1.jpg".to_string(),
category: "Images".to_string(),
sub_category: String::new(),
},
FileCategory {
filename: "photo2.png".to_string(),
category: "Images".to_string(),
sub_category: String::new(),
},
FileCategory {
filename: "doc.pdf".to_string(),
category: "Documents".to_string(),
sub_category: String::new(),
},
],
};
assert_eq!(plan.files.len(), 3);
assert_eq!(plan.files[0].category, "Images");
assert_eq!(plan.files[2].category, "Documents");
}
#[test]
fn test_organization_plan_empty() {
let plan = OrganizationPlan { files: vec![] };
assert!(plan.files.is_empty());
}
#[test]
fn test_file_category_with_subcategory() {
let file_category = FileCategory {
filename: "project.rs".to_string(),
category: "Code".to_string(),
sub_category: "Rust".to_string(),
};
assert_eq!(file_category.filename, "project.rs");
assert_eq!(file_category.category, "Code");
assert_eq!(file_category.sub_category, "Rust");
}
// ============================================================================
// EDGE CASE TESTS
// ============================================================================
#[test]
fn test_handle_offline_organization_hidden_files() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&[".hidden.txt", ".config.json"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec![".hidden.txt".to_string(), ".config.json".to_string()],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}
#[test]
fn test_handle_offline_organization_multiple_dots_in_filename() {
let (_temp_dir, dir_path) =
setup_test_dir_with_files(&["file.name.with.dots.pdf", "archive.tar.gz"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(
vec![
"file.name.with.dots.pdf".to_string(),
"archive.tar.gz".to_string(),
],
&dir_path,
);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}
#[test]
fn test_handle_offline_organization_single_file() {
let (_temp_dir, dir_path) = setup_test_dir_with_files(&["single.jpg"]);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(vec!["single.jpg".to_string()], &dir_path);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}
#[test]
fn test_handle_offline_organization_large_batch() {
// Generate 100 files with various extensions
let extensions = vec!["jpg", "pdf", "rs", "mp3", "mp4", "zip"];
let files: Vec<String> = (0..100)
.map(|i| format!("file{}.{}", i, extensions[i % extensions.len()]))
.collect();
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let (_temp_dir, dir_path) = setup_test_dir_with_files(&file_refs);
let mut undo_log = UndoLog::new();
let batch = create_file_batch(files, &dir_path);
let result = handle_offline_organization(batch, &dir_path, true, &mut undo_log);
assert!(result.is_ok());
}