#[cfg(test)]
mod utils;
#[cfg(test)]
mod project_tests {
use std::path::{Path, PathBuf};
use predicates::boolean::PredicateBooleanExt;
use tempdir::TempDir;
use test_case::test_case;
use treeflow::utils::Return;
use crate::utils::treeflow_command::TreeflowCommand;
#[test_case("project", "worktrees", "project", "worktrees", false; "project - Basic")]
#[test_case("project", "worktrees", "project", "worktrees", true; "project - Relative 2")]
#[test_case("project", "worktrees", "project/", "worktrees", false; "Project - Trailing slash")]
#[test_case("project", "worktrees", "project/subdir/../", "worktrees", false; "Project - Relative")]
#[test_case("project", "worktrees", "project", "worktrees", false; "Worktree - Basic")]
#[test_case("project", "worktrees", "project", "worktrees", true; "Worktree - Relative 3")]
#[test_case("project", "worktrees", "project", "worktrees/", false; "Worktree - Trailing slash")]
#[test_case("project", "worktrees", "project", "worktrees/subdir/../", false; "Worktree - Relative")]
fn project_add_updates_config(expected_repo: &str, expected_worktree: &str, repo: &str, worktree: &str, relative: bool) {
let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
let current_dir = TempDir::new("current_dir").expect("should be able to create temp current dir");
let current_path = current_dir.path();
let repository_path = current_path.join(repo);
let worktrees_path = current_path.join(worktree);
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(if relative { Path::new(repo) } else { repository_path.as_path() }, Some(if relative { Path::new(worktree) } else { worktrees_path.as_path() }))
.current_dir(current_path)
.cmd()
.assert()
.success()
.stdout(Return::Null {}.print());
TreeflowCommand::new(config_dir.path().to_path_buf())
.config()
.cmd()
.assert()
.success()
.stdout(format!(
"work_types = []\n\n[[projects]]\nrepository = \"{0}\"\nworktrees = \"{1}\"\n",
current_path.join(expected_repo).display(),
current_path.join(expected_worktree).display()
));
}
#[test_case(false, "project", false, "project"; "Matching absolute paths")]
#[test_case(true, "project", true, "project"; "Matching relative paths")]
#[test_case(false, "project", true, "project"; "Matching absolute / relative paths")]
#[test_case(true, "project", false, "project"; "Matching relative / absolute paths")]
#[test_case(false, ".", true, "."; "absolute / relative current dir")]
#[test_case(true, ".", false, "."; "relative / absolute current dir")]
#[test_case(true, "project/../.", false, "."; "Add path traversal")]
#[test_case(true, ".", false, "project/../."; "Remove path traversal")]
fn project_remove(add_relative: bool, add_path: &str, remove_relative: bool, remove_path: &str) {
let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
let current_dir = TempDir::new("current_dir").expect("should be able to create temp current dir");
let add_path_buf = match add_relative {
true => PathBuf::from(add_path),
false => current_dir.path().join(add_path)
};
let remove_path_buf = match remove_relative {
true => PathBuf::from(remove_path),
false => current_dir.path().join(remove_path)
};
let worktrees_path = current_dir.path().join("worktrees");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&add_path_buf, Some(&worktrees_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_remove(&remove_path_buf)
.current_dir(current_dir.path())
.cmd()
.assert()
.success()
.stdout(Return::Null { }.print());
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout("<<No projects configured>>\n");
}
#[test]
fn remove_projects_updates_list() {
let config_dir = TempDir::new("config_dir").expect("Temp dir");
let current_dir = TempDir::new("current_dir").expect("Temp dir");
let repo1_path = current_dir.path().join("repo1");
let repo2_path = current_dir.path().join("repo2");
let worktrees1_path = current_dir.path().join("worktrees1");
let worktrees2_path = current_dir.path().join("worktrees2");
std::fs::create_dir_all(&repo1_path).expect("Failed to create repo1 dir");
std::fs::create_dir_all(&repo2_path).expect("Failed to create repo2 dir");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo1_path, Some(&worktrees1_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo2_path, Some(&worktrees2_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_remove(&repo2_path)
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout(format!("{}\n", repo1_path.display()));
}
#[test]
fn list_empty_projects() {
let config_dir = TempDir::new("config_dir").expect("Temp dir");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout("<<No projects configured>>\n");
}
#[test]
fn list_multiple_projects() {
let config_dir = TempDir::new("config_dir").expect("Temp dir");
let current_dir = TempDir::new("current_dir").expect("Temp dir");
let repo1_path = current_dir.path().join("repo1");
let repo2_path = current_dir.path().join("repo2");
let repo3_path = current_dir.path().join("repo3");
let worktrees1_path = current_dir.path().join("worktrees1");
let worktrees2_path = current_dir.path().join("worktrees2");
let worktrees3_path = current_dir.path().join("worktrees3");
std::fs::create_dir_all(&repo1_path).expect("Failed to create repo1 dir");
std::fs::create_dir_all(&repo2_path).expect("Failed to create repo2 dir");
std::fs::create_dir_all(&repo3_path).expect("Failed to create repo3 dir");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo1_path, Some(&worktrees1_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo2_path, Some(&worktrees2_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo3_path, Some(&worktrees3_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout(format!("{}\n{}\n{}\n", repo1_path.display(), repo2_path.display(), repo3_path.display()));
}
#[test]
fn project_add_duplicate_path_fails() {
let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
let current_dir = TempDir::new("current_dir").expect("should be able to create temp current dir");
let repository_path = current_dir.path().to_path_buf();
let worktrees_path = current_dir.path().join("worktrees");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repository_path, Some(&worktrees_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repository_path, Some(&worktrees_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.failure()
.stderr(predicates::str::contains("already exists").or(predicates::str::contains("duplicate")));
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout(format!("{}\n", repository_path.display()));
}
#[test]
fn project_add_duplicate_worktrees_path_fails() {
let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
let current_dir = TempDir::new("current_dir").expect("should be able to create temp current dir");
let repo1_path = current_dir.path().join("repo1");
let repo2_path = current_dir.path().join("repo2");
let worktrees_path = current_dir.path().join("worktrees");
std::fs::create_dir_all(&repo1_path).expect("Failed to create repo1 dir");
std::fs::create_dir_all(&repo2_path).expect("Failed to create repo2 dir");
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo1_path, Some(&worktrees_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.success();
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_add(&repo2_path, Some(&worktrees_path))
.current_dir(current_dir.path())
.cmd()
.assert()
.failure()
.stderr(predicates::str::contains("already exists").or(predicates::str::contains("duplicate")));
TreeflowCommand::new(config_dir.path().to_path_buf())
.project_list()
.cmd()
.assert()
.success()
.stdout(format!("{}\n", repo1_path.display()));
}
}