diff options
| -rw-r--r-- | src/command/add.rs | 95 | ||||
| -rw-r--r-- | src/command/help.rs | 47 | ||||
| -rw-r--r-- | src/command/update.rs | 223 | ||||
| -rw-r--r-- | src/remote/git.rs | 120 | ||||
| -rw-r--r-- | src/remote/mod.rs | 64 | ||||
| -rw-r--r-- | test_utilities/src/lib.rs | 82 |
6 files changed, 534 insertions, 97 deletions
diff --git a/src/command/add.rs b/src/command/add.rs index d38d077..9622f81 100644 --- a/src/command/add.rs +++ b/src/command/add.rs @@ -48,3 +48,98 @@ impl super::Command for Add { "<path_to_post>\t\t\tCreates new blog post" } } + +#[cfg(test)] +mod tests { + use std::fs::create_dir_all; + + use super::*; + use crate::command::Command; + use crate::configuration::Configuration; + + use test_utilities::*; + + #[test] + fn test_add_command() { + let add = Add::new(); + + // Create all directories + let test_dir = setup_test_dir(); + + let posts_dir = test_dir.join("posts"); + create_dir_all(&posts_dir).expect("Could not create posts test directory"); + create_dir_all(&posts_dir.join("0")).expect("Could not create post 0 test directory"); + + // Create Test Files + + // Create configuration + let mut configuration = Configuration::new(); + configuration.posts_directory = posts_dir.clone(); + + // Let's ensure the initial state is OK + assert!(&posts_dir.join("0").exists()); + assert!(!&posts_dir.join("1").exists()); + assert!(!&posts_dir.join("2").exists()); + assert!(!&posts_dir.join("3").exists()); + + // Add 1 of 3 + add.execute(None, &configuration, "add") + .expect("Could not add a.gmi"); + assert!(!&posts_dir.join("0").exists()); + assert!(&posts_dir.join("1").exists()); + assert!(!&posts_dir.join("2").exists()); + assert!(!&posts_dir.join("3").exists()); + + // Add 2 of 3 + add.execute(None, &configuration, "add") + .expect("Could not add a.gmi"); + assert!(!&posts_dir.join("0").exists()); + assert!(!&posts_dir.join("1").exists()); + assert!(&posts_dir.join("2").exists()); + assert!(!&posts_dir.join("3").exists()); + + // Add 3 of 3 + add.execute(None, &configuration, "add") + .expect("Could not add a.gmi"); + assert!(!&posts_dir.join("0").exists()); + assert!(!&posts_dir.join("1").exists()); + assert!(!&posts_dir.join("2").exists()); + assert!(!&posts_dir.join("3").exists()); + + cleanup_test_dir(&test_dir); + } + + #[test] + fn add_before_dependencies() { + let add = Add::new(); + let dependencies = add.before_dependencies(); + + assert_eq!(dependencies.len(), 1); + assert_eq!(dependencies[0].command(), "sync-down"); + } + + #[test] + fn add_after_dependencies() { + let add = Add::new(); + let dependencies = add.after_dependencies(); + + assert_eq!(dependencies.len(), 3); + assert_eq!(dependencies[0].command(), "update"); + assert_eq!(dependencies[1].command(), "generate"); + assert_eq!(dependencies[2].command(), "sync-up"); + } + + // These two tests feel pointless but I'm doing it for the coverage :p + + #[test] + fn add_command_output() { + let add = Add::new(); + add.command(); + } + + #[test] + fn add_help_output() { + let add = Add::new(); + add.help(); + } +} diff --git a/src/command/help.rs b/src/command/help.rs index b682fc0..da5831c 100644 --- a/src/command/help.rs +++ b/src/command/help.rs @@ -38,3 +38,50 @@ impl super::Command for Help { "\t\t\t\tPrints this help" } } + +#[cfg(test)] +mod tests { + + use super::*; + use crate::command::Command; + use crate::configuration::Configuration; + + #[test] + fn test_add_command() { + let help = Help::new(); + + let configuration = Configuration::new(); + help.execute(None, &configuration, "") + .expect("Could not call help"); + } + + #[test] + fn help_before_dependencies() { + let help = Help::new(); + let dependencies = help.before_dependencies(); + + assert_eq!(dependencies.len(), 0); + } + + #[test] + fn help_after_dependencies() { + let help = Help::new(); + let dependencies = help.after_dependencies(); + + assert_eq!(dependencies.len(), 0); + } + + // These two tests feel pointless but I'm doing it for the coverage :p + + #[test] + fn help_command_output() { + let help = Help::new(); + help.command(); + } + + #[test] + fn help_help_output() { + let help = Help::new(); + help.help(); + } +} diff --git a/src/command/update.rs b/src/command/update.rs index 54005f8..fedec13 100644 --- a/src/command/update.rs +++ b/src/command/update.rs @@ -40,6 +40,7 @@ impl Update { let entry_target = target.join(entry_name); if entry_type.is_dir() { + create_dir_all(&entry_target)?; Update::archive(&entry_source, &entry_target)?; } else { copy(&entry_source, &entry_target)?; @@ -110,3 +111,225 @@ impl super::Command for Update { "<path_to_post>\t\tUpdates latest blog post" } } + +#[cfg(test)] +mod tests { + use std::fs::{create_dir_all, read_dir}; + use std::path::PathBuf; + + use super::*; + use crate::command::Command; + use crate::configuration::Configuration; + + use test_utilities::*; + + #[test] + fn test_update_command() { + let update = Update::new(); + + // Create all directories + let test_dir = setup_test_dir(); + + // Reference but don't create the directories + let posts_dir = test_dir.join("posts"); + let archive_dir = test_dir.join("archive"); + + // Create configuration + let mut configuration = Configuration::new(); + configuration.posts_directory = posts_dir.clone(); + configuration.archive_directory = archive_dir.clone(); + + // Create Test File + let test_file = test_dir.join("cool-test-file-😎.gmi"); + let test_file_string = test_file.display().to_string(); + create_test_file(&test_file, "# I try."); + + // Update 1 of 2 + update.execute(Some(&test_file_string), &configuration, "update") + .expect("Could not update a .gmi"); + assert!(&posts_dir.join("0").exists()); + assert_file_contents(&posts_dir.join("0/cool-test-file-😎.gmi"), "# I try."); + + let entries = read_dir(&archive_dir) + .expect("Could not read archive"); + let mut found_path = PathBuf::new(); + for entry in entries { + let entry = entry + .expect("Could not read entry"); + found_path = entry.path(); + assert_file_contents(&found_path.join("cool-test-file-😎.gmi"), "# I try."); + assert!(&found_path.join("metadata.json").exists()); + } + + // Create Second Test File + let second_test_file = test_dir.join("cooler-test-file-😎.gmi"); + let second_test_file_string = second_test_file.display().to_string(); + create_test_file(&second_test_file, "# I REALLY try."); + + // Update 2 of 2 + update.execute(Some(&second_test_file_string), &configuration, "update") + .expect("Could not update a .gmi"); + assert_file_contents(&posts_dir.join("0/cooler-test-file-😎.gmi"), "# I REALLY try."); + assert_file_contents(&found_path.join("cooler-test-file-😎.gmi"), "# I REALLY try."); + + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_write_metadata_fail() { + // Create all directories + let test_dir = setup_test_dir(); + + // Reference but don't create the directories + let metadata_dir = test_dir.join("metadata/nested/cannot/write"); + + // Create metadata + let metadata = Metadata { + id: "1234".to_string(), + created_on: 1234 + }; + + // Update 1 of 2 + let result = Update::write_metadata(&metadata, &metadata_dir); + assert!(result.is_err()); + + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_recursive_archive() { + // Create all directories + let test_dir = setup_test_dir(); + + // Create the directories + let source_dir = test_dir.join("source"); + let target_dir = test_dir.join("target"); + let nested_dir = source_dir.join("nested"); + create_dir_all(&nested_dir).expect("Could not create source dir."); + create_dir_all(&target_dir).expect("Could not create target dir."); + + // Create the two test files. + let test_file = source_dir.join("not_nested.gmi"); + let nested_test_file = nested_dir.join("very_much_nested.gmi"); + create_test_file(&test_file, "# Unlike a Bird"); + create_test_file(&nested_test_file, "# Very much like a Bird"); + + Update::archive(&source_dir, &target_dir) + .expect("Could not archive the test directories"); + + assert_file_contents(&target_dir.join("not_nested.gmi"), "# Unlike a Bird"); + assert_file_contents(&target_dir.join("nested/very_much_nested.gmi"), "# Very much like a Bird"); + + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_update_fails_on_directory() { + let update = Update::new(); + + // Create all directories + let test_dir = setup_test_dir(); + + // Reference but don't create the directories + let nested_dir = test_dir.join("nested"); + let posts_dir = test_dir.join("posts"); + let archive_dir = test_dir.join("archive"); + create_dir_all(&nested_dir).expect("Could not create nested dir."); + + // Create configuration + let mut configuration = Configuration::new(); + configuration.posts_directory = posts_dir.clone(); + configuration.archive_directory = archive_dir.clone(); + + // Create Test File + let test_file = nested_dir.join("cool-test-file-😎.gmi"); + create_test_file(&test_file, "# I try."); + let nested_string = nested_dir.display().to_string(); + + let result = update.execute(Some(&nested_string), &configuration, "update"); + assert!(result.is_err()); + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_update_fails_on_missing_input() { + let update = Update::new(); + + // Create all directories + let test_dir = setup_test_dir(); + + // Reference but don't create the directories + let posts_dir = test_dir.join("posts"); + let archive_dir = test_dir.join("archive"); + + // Create configuration + let mut configuration = Configuration::new(); + configuration.posts_directory = posts_dir.clone(); + configuration.archive_directory = archive_dir.clone(); + + // Add 1 of 3 + let result = update.execute(None, &configuration, "update"); + assert!(result.is_err()); + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_update_fails_if_file_does_not_exist() { + let update = Update::new(); + + // Create all directories + let test_dir = setup_test_dir(); + + // Reference but don't create the directories + let posts_dir = test_dir.join("posts"); + let archive_dir = test_dir.join("archive"); + + // Create configuration + let mut configuration = Configuration::new(); + configuration.posts_directory = posts_dir.clone(); + configuration.archive_directory = archive_dir.clone(); + + // Create Test File + let test_file = test_dir.join("cool-secret-test-file-😎.gmi"); + let test_file_string = test_file.display().to_string(); + + // Add 1 of 3 + let result = update.execute(Some(&test_file_string), &configuration, "update"); + assert!(result.is_err()); + + cleanup_test_dir(&test_dir); + } + + #[test] + fn update_before_dependencies() { + let update = Update::new(); + let dependencies = update.before_dependencies(); + + assert_eq!(dependencies.len(), 1); + assert_eq!(dependencies[0].command(), "sync-down"); + } + + #[test] + fn update_after_dependencies() { + let update = Update::new(); + let dependencies = update.after_dependencies(); + + assert_eq!(dependencies.len(), 2); + assert_eq!(dependencies[0].command(), "generate"); + assert_eq!(dependencies[1].command(), "sync-up"); + } + + // These two tests feel pointless but I'm doing it for the coverage :p + + #[test] + fn update_command_output() { + let update = Update::new(); + update.command(); + } + + #[test] + fn update_help_output() { + let update = Update::new(); + update.help(); + } +} diff --git a/src/remote/git.rs b/src/remote/git.rs index 75e6292..912d8c1 100644 --- a/src/remote/git.rs +++ b/src/remote/git.rs @@ -13,10 +13,8 @@ impl Git { impl super::Remote for Git { fn can_handle(&self, remote: &str) -> bool { - let command = format!("git ls-remote {remote}"); - match Command::new("sh") - .arg("-c") - .arg(&command) + match Command::new("git") + .args(["ls-remote", remote]) .stdout(Stdio::null()) .stderr(Stdio::null()) .status() @@ -32,25 +30,18 @@ impl super::Remote for Git { .map_err(|_| Error::new(Other, "Invalid time"))? .as_millis(); - let commands = vec![ - format!("cd {} && git init -b main", directory.display()), - format!("cd {} && git add .", directory.display()), - format!( - "cd {} && git commit --allow-empty -m blog-sync-up-{}", - directory.display(), - timestamp - ), - format!( - "cd {} && git push {} main --force", - directory.display(), - remote - ), + let commit_name = format!("blog-sync-up-{}", timestamp); + let command_arguments = vec![ + vec!["init", "-b", "main"], + vec!["add", "."], + vec!["commit", "--allow-empty", "-m", &commit_name], + vec!["push", &remote, "main", "--force"], ]; - for command in commands { - Command::new("sh") - .arg("-c") - .arg(&command) + for arguments in command_arguments { + Command::new("git") + .current_dir(&directory) + .args(&arguments) .stdout(Stdio::null()) .stderr(Stdio::null()) .status() @@ -61,17 +52,17 @@ impl super::Remote for Git { } fn sync_down(&self, remote: &str, directory: &Path) -> Result<()> { - let commands = vec![ - format!("cd {} && git init -b main", directory.display()), - format!("cd {} && git checkout .", directory.display()), - format!("cd {} && git clean . -f", directory.display()), - format!("cd {} && git pull {} main", directory.display(), remote), + let command_arguments = vec![ + vec!["init", "-b", "main"], + vec!["checkout", "."], + vec!["clean", ".", "-f"], + vec!["pull", &remote, "main"], ]; - for command in commands { - Command::new("sh") - .arg("-c") - .arg(&command) + for arguments in command_arguments { + Command::new("git") + .current_dir(&directory) + .args(&arguments) .stdout(Stdio::null()) .stderr(Stdio::null()) .status() @@ -84,71 +75,12 @@ impl super::Remote for Git { #[cfg(test)] mod tests { use std::fs::create_dir_all; - use std::path::Path; - use std::process::{Command, Stdio}; use super::*; use crate::remote::Remote; use test_utilities::*; - fn create_remote(path: &Path) { - let remote_path = path.display(); - Command::new("sh") - .arg("-c") - .arg(format!("cd {remote_path} && git init --bare")) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .expect("Failed to initialize bare repository"); - } - - fn assert_file_does_not_exist(repo_path: &str, file_path: &str) { - let output = Command::new("sh") - .arg("-c") - .arg(format!("cd {repo_path} && git show main:{file_path}")) - .stderr(Stdio::null()) - .output() - .expect("Failed to read from repo"); - - assert!(output.status.code().unwrap() != 0); - } - - fn assert_file_in_repo_with_contents(repo_path: &str, file_path: &str, contents: &str) { - let output = Command::new("sh") - .arg("-c") - .arg(format!("cd {repo_path} && git show main:{file_path}")) - .stderr(Stdio::null()) - .output() - .expect("Failed to read from repo"); - - assert!(String::from_utf8(output.stdout) - .map(|file| file.trim() == contents) - .unwrap_or(false)); - } - - fn commit_file_to_remote(repo_path: &str, file_path: &str, contents: &str) { - let test_dir = setup_test_dir(); - let test_dir_display = test_dir.display(); - - Command::new("sh") - .arg("-c") - .arg(format!( - "cd {test_dir_display} && \ - git clone {repo_path} . && \ - echo '{contents}' > {file_path} && \ - git add {file_path} && \ - git -c user.name='test' -c user.email='test@example.com' commit -m 'test commit' && \ - git push origin main", - )) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .expect("Failed to commit file"); - - cleanup_test_dir(&test_dir); - } - #[test] fn test_can_handle_git_remotes() { let git = Git::new(); @@ -201,7 +133,7 @@ mod tests { git.sync_up(&remote_dir_as_string, &local_dir) .expect("Could not sync up to remote"); assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); - assert_file_does_not_exist(&remote_dir_as_string, "file2.txt"); + assert_file_in_repo_does_not_exist(&remote_dir_as_string, "file2.txt"); create_test_file(&local_dir.join("file2.txt"), "I also exist now, btw"); git.sync_up(&remote_dir_as_string, &local_dir) @@ -219,8 +151,8 @@ mod tests { fn test_remote_up_force_pushes() { let git = Git::new(); let test_dir = setup_test_dir(); - let local_dir = test_dir.join("local"); - let remote_dir = test_dir.join("remote"); + let local_dir = test_dir.join("gitlocal"); + let remote_dir = test_dir.join("gitremote"); create_dir_all(&local_dir).expect("Could not create local test directory"); create_dir_all(&remote_dir).expect("Could not create remote test directory"); @@ -229,7 +161,7 @@ mod tests { let remote_dir_as_string = remote_dir.display().to_string(); commit_file_to_remote(&remote_dir_as_string, "boo.txt", "A ghost"); - assert_file_does_not_exist(&remote_dir_as_string, "file1.txt"); + assert_file_in_repo_does_not_exist(&remote_dir_as_string, "file1.txt"); assert_file_in_repo_with_contents(&remote_dir_as_string, "boo.txt", "A ghost"); create_test_file(&local_dir.join("file1.txt"), "I exist"); @@ -237,7 +169,7 @@ mod tests { .expect("Could not sync up to remote"); assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); - assert_file_does_not_exist(&remote_dir_as_string, "boo.txt"); + assert_file_in_repo_does_not_exist(&remote_dir_as_string, "boo.txt"); cleanup_test_dir(&test_dir); } diff --git a/src/remote/mod.rs b/src/remote/mod.rs index 9782b0d..1a2421d 100644 --- a/src/remote/mod.rs +++ b/src/remote/mod.rs @@ -70,6 +70,7 @@ fn read_remote(file_path: &Path) -> Option<String> { #[cfg(test)] mod tests { + use std::fs::create_dir_all; use super::*; @@ -102,4 +103,67 @@ mod tests { cleanup_test_dir(&test_dir); } + + #[test] + fn test_syncs_remote_up() { + let test_dir = setup_test_dir(); + let local_dir = test_dir.join("modlocal"); + let remote_dir = test_dir.join("modremote"); + create_dir_all(&local_dir).expect("Could not create local test directory"); + create_dir_all(&remote_dir).expect("Could not create remote test directory"); + + create_remote(&remote_dir); + + let remote_dir_as_string = remote_dir.display().to_string(); + create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); + + create_test_file(&local_dir.join("file1.txt"), "I exist"); + sync_up(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); + assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); + assert_file_in_repo_does_not_exist(&remote_dir_as_string, "file2.txt"); + + create_test_file(&local_dir.join("file2.txt"), "I also exist now, btw"); + sync_up(&local_dir, &test_dir.join("remoteconfig")).expect("Could not sync up to remote"); + assert_file_in_repo_with_contents(&remote_dir_as_string, "file1.txt", "I exist"); + assert_file_in_repo_with_contents( + &remote_dir_as_string, + "file2.txt", + "I also exist now, btw", + ); + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_syncs_remote_down() { + let test_dir = setup_test_dir(); + let local_dir = test_dir.join("modownlocal"); + let remote_dir = test_dir.join("modownremote"); + create_dir_all(&local_dir).expect("Could not create local test directory"); + create_dir_all(&remote_dir).expect("Could not create remote test directory"); + + create_remote(&remote_dir); + + let remote_dir_as_string = remote_dir.display().to_string(); + create_test_file(&test_dir.join("remoteconfig"), &remote_dir_as_string); + + commit_file_to_remote(&remote_dir_as_string, "file 1.txt", "I exist, but remotely"); + assert!(!&local_dir.join("file 1.txt").exists()); + assert!(!&local_dir.join("file2.txt").exists()); + sync_down(&local_dir, &test_dir.join("remoteconfig")) + .expect("Could not sync down from remote"); + assert_file_contents(&local_dir.join("file 1.txt"), "I exist, but remotely"); + assert!(!&local_dir.join("file2.txt").exists()); + + commit_file_to_remote( + &remote_dir_as_string, + "file2.txt", + "I also exist, but remotely", + ); + assert!(!&local_dir.join("file2.txt").exists()); + sync_down(&local_dir, &test_dir.join("remoteconfig")) + .expect("Could not sync down from remote"); + assert_file_contents(&local_dir.join("file 1.txt"), "I exist, but remotely"); + assert_file_contents(&local_dir.join("file2.txt"), "I also exist, but remotely"); + cleanup_test_dir(&test_dir); + } } diff --git a/test_utilities/src/lib.rs b/test_utilities/src/lib.rs index dc6fe75..bcbf1f0 100644 --- a/test_utilities/src/lib.rs +++ b/test_utilities/src/lib.rs @@ -1,9 +1,9 @@ use std::env::temp_dir; use std::fs::{create_dir_all, read_to_string, remove_dir_all, File}; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::process::{id, Command, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; -use std::process::id; pub fn setup_test_dir() -> PathBuf { let timestamp = SystemTime::now() @@ -11,7 +11,8 @@ pub fn setup_test_dir() -> PathBuf { .unwrap() .as_nanos(); let process_id = id(); - let test_dir = temp_dir().join(format!("blog_test_{}_{}", timestamp, process_id)); + let random = random_string(); + let test_dir = temp_dir().join(format!("blog_test_{timestamp}_{process_id}_{random}")); create_dir_all(&test_dir) .expect("Could not create test directory"); test_dir @@ -42,3 +43,78 @@ pub fn assert_file_does_not_contain(path: &PathBuf, expected: &str) { let content = read_to_string(path).unwrap(); assert!(!content.contains(expected)); } + +pub fn create_remote(path: &Path) { + Command::new("git") + .current_dir(&path) + .args(["init", "--bare"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("Failed to initialize bare repository"); +} + +pub fn assert_file_in_repo_does_not_exist(repo_path: &str, file_path: &str) { + let output = Command::new("git") + .current_dir(&repo_path) + .args(["show", &format!("main:{file_path}")]) + .stderr(Stdio::null()) + .output() + .expect("Failed to read from repo"); + + assert!(output.status.code().unwrap() != 0); +} + +pub fn assert_file_in_repo_with_contents(repo_path: &str, file_path: &str, contents: &str) { + let output = Command::new("git") + .current_dir(&repo_path) + .args(["show", &format!("main:{file_path}")]) + .stderr(Stdio::null()) + .output() + .expect("Failed to read from repo"); + + assert!(String::from_utf8(output.stdout) + .map(|file| file.trim() == contents) + .unwrap_or(false)); +} + +pub fn commit_file_to_remote(repo_path: &str, file_path: &str, contents: &str) { + let test_dir = setup_test_dir(); + + Command::new("git") + .current_dir(&test_dir) + .args(["clone", &repo_path, "."]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("Failed to clone repo"); + create_test_file(&test_dir.join(file_path), contents); + Command::new("git") + .current_dir(&test_dir) + .args(["add", file_path]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("Failed to add file to repo"); + Command::new("git") + .current_dir(&test_dir) + .args(["-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-m", "test commit"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("Failed to commit file to repo"); + Command::new("git") + .current_dir(&test_dir) + .args(["push", "origin", "main"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("Failed to push repo"); + + cleanup_test_dir(&test_dir); +} + +fn random_string() -> String { + let x = Box::new(0); + format!("{:p}", x) +} |