mod integrated_test { use std::{ fs::{DirBuilder, File}, io::{self, BufWriter, Write}, }; use anyhow::{Context, Ok, Result}; use assert_cmd::{assert::OutputAssertExt, Command}; use dirs::home_dir; use git2::Repository; use log::trace; use predicates::{boolean::PredicateBooleanExt, prelude::predicate}; /// Setup global gitconfig if it doesn't exist. /// /// # Errors /// /// This function will return an error if it failed to get home directory. fn setup_gitconfig() -> Result<()> { let f = match File::create_new( home_dir() .context("Failed to find home directory")? .join(".gitconfig"), ) { io::Result::Ok(f) => f, io::Result::Err(_err) => return Ok(()), }; let mut buf = BufWriter::new(f); buf.write_all( r#" [user] email = "test@example.com" name = "testuser" "# .as_bytes(), )?; Ok(()) } #[test] fn single_device() -> Result<()> { let config_dir = assert_fs::TempDir::new()?; setup_gitconfig()?; // init let mut cmd = Command::cargo_bin("xdbm")?; cmd.arg("-c") .arg(config_dir.path()) .arg("init") .arg("testdev"); cmd.assert().success().stdout(predicate::str::contains("")); assert_eq!( std::fs::read_to_string(config_dir.path().join("devname"))?, "testdev\n" ); // storage add let storage = assert_fs::TempDir::new()?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir.path()) .arg("storage") .arg("add") .arg("online") .arg("--provider") .arg("sample_provider") .arg("--capacity") .arg("1000000000000") .arg("--alias") .arg("alias") .arg("online_storage") .arg(storage.path()) .assert() .success(); // storage list Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir.path()) .arg("storage") .arg("list") .assert() .success() .stdout(predicate::str::contains("online_storage")); // backup add let target_from = storage.join("foo/bar"); let target_to = storage.join("aaa/bbb/ccc"); DirBuilder::new() .recursive(true) .create(target_from.clone())?; DirBuilder::new() .recursive(true) .create(target_to.clone())?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir.path()) .arg("backup") .arg("add") .arg("--src") .arg(target_from) .arg("--dest") .arg(target_to) .arg("sample_backup") .arg("external") .arg("rsync") .arg("with some note") .assert() .success(); // backup list Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir.path()) .arg("backup") .arg("list") .assert() .success() .stdout(predicate::str::contains("sample_backup")); Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir.path()) .arg("backup") .arg("done") .arg("sample_backup") .arg("0") .assert() .success(); Ok(()) } #[test] fn two_devices_with_same_name() -> Result<()> { // 1st device let config_dir_1 = assert_fs::TempDir::new()?; setup_gitconfig()?; let mut cmd1 = Command::cargo_bin("xdbm")?; cmd1.arg("-c") .arg(config_dir_1.path()) .arg("init") .arg("first"); cmd1.assert().success().stdout(predicate::str::contains("")); // bare-repo let bare_repo_dir = assert_fs::TempDir::new()?; let _bare_repo = Repository::init_bare(&bare_repo_dir)?; // push to bare repository let repo_1 = Repository::open(&config_dir_1)?; let upstream_name = "remote"; let mut repo_1_remote = repo_1.remote(upstream_name, bare_repo_dir.path().to_str().unwrap())?; repo_1_remote.push(&[repo_1.head().unwrap().name().unwrap()], None)?; trace!("bare repo {:?}", bare_repo_dir.display()); println!("{:?}", bare_repo_dir.read_dir()?); // set up upstream branch let (mut repo_1_branch, _branch_type) = repo_1.branches(None)?.next().unwrap()?; println!("head {}", repo_1.head().unwrap().name().unwrap()); repo_1_branch.set_upstream(Some( format!( "{}/{}", upstream_name, repo_1_branch.name().unwrap().unwrap() ) .as_str(), ))?; // 2nd device let config_dir_2 = assert_fs::TempDir::new()?; setup_gitconfig()?; let mut cmd2 = Command::cargo_bin("xdbm")?; cmd2.arg("-c") .arg(config_dir_2.path()) .arg("init") .arg("first") .arg("-r") .arg(bare_repo_dir.path()); cmd2.assert().failure(); Ok(()) } #[test] fn directory_without_parent() -> Result<()> { // 1st device let config_dir_1 = assert_fs::TempDir::new()?; setup_gitconfig()?; let mut cmd1 = Command::cargo_bin("xdbm")?; cmd1.arg("-c") .arg(config_dir_1.path()) .arg("init") .arg("first"); cmd1.assert().success().stdout(predicate::str::contains("")); // add storage let sample_storage = assert_fs::TempDir::new()?; let mut cmd_add_storage = Command::cargo_bin("xdbm")?; cmd_add_storage .arg("-c") .arg(config_dir_1.path()) .arg("storage") .arg("add") .arg("directory") .arg("--alias") .arg("gdrive") .arg("gdrive") .arg(sample_storage.path()); cmd_add_storage .assert() .failure() .stderr(predicate::str::contains("No storages found")); Ok(()) } #[test] fn two_devices() -> Result<()> { // 1st device // // devices: first let config_dir_1 = assert_fs::TempDir::new()?; setup_gitconfig()?; let mut cmd1 = Command::cargo_bin("xdbm")?; cmd1.arg("-c") .arg(config_dir_1.path()) .arg("init") .arg("first"); cmd1.assert().success().stdout(predicate::str::contains("")); // bare-repo let bare_repo_dir = assert_fs::TempDir::new()?; let _bare_repo = Repository::init_bare(&bare_repo_dir)?; // push to bare repository let repo_1 = Repository::open(&config_dir_1)?; let upstream_name = "remote"; let mut repo_1_remote = repo_1.remote(upstream_name, bare_repo_dir.path().to_str().unwrap())?; repo_1_remote.push(&[repo_1.head().unwrap().name().unwrap()], None)?; trace!("bare repo {:?}", bare_repo_dir.display()); println!("{:?}", bare_repo_dir.read_dir()?); // set up upstream branch let (mut repo_1_branch, _branch_type) = repo_1.branches(None)?.next().unwrap()?; repo_1_branch.set_upstream(Some( format!( "{}/{}", upstream_name, repo_1_branch.name().unwrap().unwrap() ) .as_str(), ))?; // 2nd device // // devices: first, second let config_dir_2 = assert_fs::TempDir::new()?; let mut cmd2 = Command::cargo_bin("xdbm")?; cmd2.arg("-c") .arg(config_dir_2.path()) .arg("init") .arg("second") .arg("-r") .arg(bare_repo_dir.path()); cmd2.assert().success().stdout(predicate::str::contains("")); assert_eq!( std::fs::read_to_string(config_dir_2.path().join("devname"))?, "second\n" ); assert!( std::fs::read_to_string(config_dir_2.path().join("devices.yml"))?.contains("first") ); assert!( std::fs::read_to_string(config_dir_2.path().join("devices.yml"))?.contains("second") ); assert!(config_dir_2.join("backups").join("first.yml").exists()); assert!(config_dir_2.join("backups").join("second.yml").exists()); // sync std::process::Command::new("git") .arg("push") .current_dir(&config_dir_2) .assert() .success(); // let repo_2 = Repository::open(config_dir_2)?; // // return Err(anyhow!("{:?}", repo_2.remotes()?.iter().collect::>())); // let mut repo_2_remote = repo_2.find_remote(repo_2.remotes()?.get(0).unwrap())?; // repo_2_remote.push(&[] as &[&str], None)?; std::process::Command::new("git") .arg("pull") .current_dir(&config_dir_1) .assert() .success(); // Add storage // // devices: first, second // storages: // - gdrive @ sample_storage (online) // - first: sample_storage let sample_storage = assert_fs::TempDir::new()?; let mut cmd_add_storage_1 = Command::cargo_bin("xdbm")?; cmd_add_storage_1 .arg("-c") .arg(config_dir_1.path()) .arg("storage") .arg("add") .arg("online") .arg("--provider") .arg("google") .arg("--capacity") .arg("15000000000") .arg("--alias") .arg("gdrive") .arg("gdrive1") .arg(sample_storage.path()); cmd_add_storage_1 .assert() .success() .stdout(predicate::str::contains("")); // Add storage (directory) // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first let sample_directory = &sample_storage.join("foo").join("bar"); DirBuilder::new().recursive(true).create(sample_directory)?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_1.path()) .arg("storage") .arg("add") .arg("directory") .arg("--alias") .arg("docs") .arg("gdrive_docs") .arg(sample_directory) .assert() .success() .stdout(predicate::str::contains("")); assert!( std::fs::read_to_string(config_dir_1.join("storages.yml"))?.contains("parent: gdrive1") ); std::process::Command::new("git") .arg("push") .current_dir(&config_dir_1) .assert() .success(); std::process::Command::new("git") .arg("pull") .current_dir(&config_dir_2) .assert() .success(); // bind // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("storage") .arg("bind") .arg("--alias") .arg("gdocs") .arg("--path") .arg(sample_directory) .arg("gdrive_docs") .assert() .success() .stdout(predicate::str::contains("")); // storage 3 // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - second: sample_storage_2 let sample_storage_2 = assert_fs::TempDir::new()?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("storage") .arg("add") .arg("online") .arg("--provider") .arg("me") .arg("--capacity") .arg("1000000000000") .arg("--alias") .arg("nas") .arg("nas") .arg(sample_storage_2.path()) .assert() .success(); // storage list Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("storage") .arg("list") .arg("-l") .assert() .success() .stdout(predicate::str::contains("gdrive_docs").and(predicate::str::contains("nas"))); // backup add // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs let backup_src = &sample_storage_2.join("foo").join("bar"); DirBuilder::new().recursive(true).create(backup_src)?; let backup_dest = &sample_directory.join("docs"); DirBuilder::new().recursive(true).create(backup_dest)?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("add") .arg("--src") .arg(backup_src) .arg("--dest") .arg(backup_dest) .arg("foodoc") .arg("external") .arg("rsync") .arg("note: nonsense") .assert() .success(); // backup add but with existing name Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("add") .arg("--src") .arg(backup_src) .arg("--dest") .arg(backup_dest) .arg("foodoc") .arg("external") .arg("rsync") .arg("note: nonsense") .assert() .failure() .stderr(predicate::str::contains("already")); // backup list Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("list") .assert() .success() .stdout( predicate::str::contains("foodoc") .and(predicate::str::contains("nas")) .and(predicate::str::contains("gdrive_docs")) .and(predicate::str::contains("---")), ); // backup done // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs (done 1) Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("done") .arg("foodoc") .arg("0") .assert() .success(); // backup list after backup done Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("list") .assert() .success() .stdout( predicate::str::contains("foodoc") .and(predicate::str::contains("nas")) .and(predicate::str::contains("gdrive_docs")) .and(predicate::str::contains("---").not()), ); // status Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .assert() .success(); Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .arg("-s") .arg(backup_src.clone().join("foo")) .assert() .success() .stdout(predicate::str::contains("nas").and(predicate::str::contains("foodoc").not())); Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .arg("-sb") .arg(backup_src.clone().join("foo")) .assert() .success() .stdout( predicate::str::contains("nas") .and(predicate::str::contains("second")) .and(predicate::str::contains("foodoc")), ); Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .arg("-sb") .arg(backup_src.clone().parent().unwrap()) .assert() .success() .stdout( predicate::str::contains("nas") .and(predicate::str::contains("second").not()) .and(predicate::str::contains("foodoc").not()), ); std::process::Command::new("git") .arg("push") .current_dir(&config_dir_2) .assert() .success(); std::process::Command::new("git") .arg("pull") .current_dir(&config_dir_1) .assert() .success(); // bind // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - first: sample_storage_2_first_path // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs (done 1) let sample_storage_2_first_path = assert_fs::TempDir::new()?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_1.path()) .arg("storage") .arg("bind") .arg("--alias") .arg("sample2") .arg("--path") .arg(sample_storage_2_first_path.path()) .arg("nas") .assert() .success() .stdout(predicate::str::contains("")); // backup add // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - first: sample_storage_2_first_path // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs (done 1) // - abcdbackup: first // - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh let backup_src = &sample_storage_2_first_path.join("abcd").join("efgh"); DirBuilder::new().recursive(true).create(backup_src)?; let backup_dest = &sample_storage.join("Downloads").join("abcd").join("efgh"); DirBuilder::new().recursive(true).create(backup_dest)?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_1.path()) .arg("backup") .arg("add") .arg("--src") .arg(backup_src) .arg("--dest") .arg(backup_dest) .arg("abcdbackup") .arg("external") .arg("rsync") .arg("note: nonsense") .assert() .success(); // backup add // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - first: sample_storage_2_first_path // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs (done 1) // - abcdbackup: first // - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh // - abcdsubbackup: first // - sample_storage_2_first_path/abcd/efgh/sub -> sample_storage/Downloads/abcd/efgh/sub let backup_src = &sample_storage_2_first_path .join("abcd") .join("efgh") .join("sub"); DirBuilder::new().recursive(true).create(backup_src)?; let backup_dest = &sample_storage .join("Downloads") .join("abcd") .join("efgh") .join("sub"); DirBuilder::new().recursive(true).create(backup_dest)?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_1.path()) .arg("backup") .arg("add") .arg("--src") .arg(backup_src) .arg("--dest") .arg(backup_dest) .arg("abcdsubbackup") .arg("external") .arg("rsync") .arg("note: only subdirectory") .assert() .success(); std::process::Command::new("git") .arg("push") .current_dir(&config_dir_1) .assert() .success(); std::process::Command::new("git") .arg("pull") .current_dir(&config_dir_2) .assert() .success(); // backup add // // devices: first, second // storages: // - gdrive (online) // - first: sample_storage // - gdrive_docs (subdir of sample_storage/foo/bar) // - first // - second: sample_directory // - nas (online) // - first: sample_storage_2_first_path // - second: sample_storage_2 // backups: // - foodoc: second // - sample_storage_2/foo/bar -> sample_directory/docs (done 1) // - abcdbackup: first // - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh // - abcdsubbackup: first // - sample_storage_2_first_path/abcd/efgh/sub -> sample_storage/Downloads/abcd/efgh/sub // - abcdbackup2: second // - sample_storage_2/abcd/efgh -> sample_directory/Downloads/abcd/efgh let backup_src = &sample_storage_2.join("abcd").join("efgh"); DirBuilder::new().recursive(true).create(backup_src)?; let backup_dest = &sample_directory .join("Downloads") .join("abcd") .join("efgh"); DirBuilder::new().recursive(true).create(backup_dest)?; Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("backup") .arg("add") .arg("--src") .arg(backup_src) .arg("--dest") .arg(backup_dest) .arg("abcdbackup2") .arg("external") .arg("rsync") .arg("note: only subdirectory") .assert() .success(); // status Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .arg("-sb") .arg(backup_src) .assert() .success() .stdout( predicate::str::contains("nas") .and(predicate::str::contains("first")) .and(predicate::str::contains("abcdbackup")) .and(predicate::str::contains("abcdsubbackup").not()) .and(predicate::str::contains("second")) .and(predicate::str::contains("abcdbackup2")), ); Command::cargo_bin("xdbm")? .arg("-c") .arg(config_dir_2.path()) .arg("status") .arg("-sb") .arg(backup_src.join("sub")) .assert() .success() .stdout( predicate::str::contains("nas") .and(predicate::str::contains("first")) .and(predicate::str::contains("abcdbackup")) .and(predicate::str::contains("abcdsubbackup")) .and(predicate::str::contains("second")) .and(predicate::str::contains("abcdbackup2")), ); Ok(()) } }