diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d724e8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Rust / Cargo +/target +**/*.rs.bk +Cargo.lock +*.pdb + +# IDEs +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Project-specific +info/ +CLAUDE.md + +# Test artifacts +/test/testverzeichnis + +# Temporary files +*.tmp +*.log +*.bak + diff --git a/Cargo.lock b/Cargo.lock index c0dc258..38641d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "NameToUnix" version = "0.2.0" dependencies = [ "anyhow", + "assert_cmd", "assert_fs", "clap", "dirs", @@ -93,6 +94,21 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "assert_cmd" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "assert_fs" version = "1.1.2" @@ -127,6 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -840,6 +857,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index a491460..71041b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ itertools = "0.12.1" # Erweiterte Iterator-Funktionalität tempfile = "3.10.1" # Temporäre Dateien für Tests assert_fs = "1.1.1" # Dateisystem-Assertions für Tests predicates = "3.1.0" # Prädikate für Tests +assert_cmd = "2.0" # Command-Line Testing [profile.release] lto = true # Link-Time-Optimierung diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..58dba45 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,194 @@ +use assert_cmd::assert::OutputAssertExt; +use assert_cmd::cargo::CommandCargoExt; +use predicates::prelude::*; +use std::fs; +use std::process::Command; +use tempfile::TempDir; + +#[test] +fn test_help_flag() { + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg("--help"); + cmd.assert() + .success() + .stdout(predicate::str::contains("Usage: ntu")); +} + +#[test] +fn test_version_flag() { + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg("--version"); + cmd.assert() + .success() + .stdout(predicate::str::contains("NameToUnix")); +} + +#[test] +fn test_dry_run_no_changes() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("test file.txt"); + fs::write(&file_path, "test content").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg("--dry-run").arg(temp_dir.path()); + cmd.assert().success(); + + // File should still have spaces (not renamed) + assert!(file_path.exists()); + assert!(!temp_dir.path().join("test_file.txt").exists()); +} + +#[test] +fn test_actual_rename() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("test file.txt"); + fs::write(&file_path, "test content").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // File should be renamed + assert!(!file_path.exists()); + assert!(temp_dir.path().join("test_file.txt").exists()); +} + +#[test] +fn test_hidden_files_preserved() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join(".gitignore"); + fs::write(&file_path, "*.tmp").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Hidden file should not be renamed + assert!(file_path.exists()); +} + +#[test] +fn test_hidden_file_with_spaces() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join(".my config"); + fs::write(&file_path, "config content").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Hidden file should be renamed but keep leading dot + assert!(!file_path.exists()); + assert!(temp_dir.path().join(".my_config").exists()); +} + +#[test] +fn test_umlaut_conversion() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("Müller.txt"); + fs::write(&file_path, "test").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Umlaut should be converted + assert!(!file_path.exists()); + assert!(temp_dir.path().join("Mueller.txt").exists()); +} + +#[test] +fn test_double_extension() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("my archive.tar.gz"); + fs::write(&file_path, "archive").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Should keep .tar.gz intact + assert!(!file_path.exists()); + assert!(temp_dir.path().join("my_archive.tar.gz").exists()); +} + +#[test] +fn test_exclude_pattern() { + let temp_dir = TempDir::new().unwrap(); + let file1 = temp_dir.path().join("test file.txt"); + let file2 = temp_dir.path().join("test file.tmp"); + fs::write(&file1, "test1").unwrap(); + fs::write(&file2, "test2").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg("--exclude") + .arg("*.tmp") + .arg(temp_dir.path()); + cmd.assert().success(); + + // .txt should be renamed, .tmp should not + assert!(!file1.exists()); + assert!(temp_dir.path().join("test_file.txt").exists()); + assert!(file2.exists()); // Excluded +} + +#[test] +fn test_quiet_mode() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("test file.txt"); + fs::write(&file_path, "test").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg("--quiet").arg(temp_dir.path()); + cmd.assert() + .success() + .stdout(predicate::str::is_empty()); +} + +#[test] +fn test_multiple_paths() { + let temp_dir1 = TempDir::new().unwrap(); + let temp_dir2 = TempDir::new().unwrap(); + let file1 = temp_dir1.path().join("file 1.txt"); + let file2 = temp_dir2.path().join("file 2.txt"); + fs::write(&file1, "test1").unwrap(); + fs::write(&file2, "test2").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir1.path()).arg(temp_dir2.path()); + cmd.assert().success(); + + // Both files should be renamed + assert!(temp_dir1.path().join("file_1.txt").exists()); + assert!(temp_dir2.path().join("file_2.txt").exists()); +} + +#[test] +fn test_parentheses_removed() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("Document (1).txt"); + fs::write(&file_path, "test").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // Parentheses should be replaced with underscores + assert!(!file_path.exists()); + assert!(temp_dir.path().join("Document_1.txt").exists()); +} + +#[test] +fn test_special_identifiers_preserved() { + let temp_dir = TempDir::new().unwrap(); + let file_path = temp_dir.path().join("C++ Guide.pdf"); + fs::write(&file_path, "test").unwrap(); + + let mut cmd = Command::cargo_bin("ntu").unwrap(); + cmd.arg(temp_dir.path()); + cmd.assert().success(); + + // C++ should be preserved + assert!(!file_path.exists()); + assert!(temp_dir.path().join("C++_Guide.pdf").exists()); +}