#![allow(clippy::missing_errors_doc)]
use anyhow::Result;
use std::path::{Path, PathBuf};
use crate::config::HookActions;
use crate::hooks::HookExecutor;
use crate::integrations::git::GitClient;
use crate::integrations::zoxide::ZoxideClient;
#[allow(dead_code)]
pub struct WorktreeService<G, H, Z>
where
G: GitClient,
H: HookExecutor,
Z: ZoxideClient,
{
git_client: G,
hook_executor: H,
zoxide_client: Z,
}
impl<G, H, Z> WorktreeService<G, H, Z>
where
G: GitClient,
H: HookExecutor,
Z: ZoxideClient,
{
#[allow(dead_code)]
pub const fn new(git_client: G, hook_executor: H, zoxide_client: Z) -> Self {
Self {
git_client,
hook_executor,
zoxide_client,
}
}
#[allow(dead_code)]
pub fn create_worktree(
&self,
branch: &str,
worktree_path: &Path,
start_point: Option<&str>,
repo_root: &Path,
hooks: &HookActions,
zoxide_enabled: bool,
) -> Result<PathBuf> {
self.git_client
.create_worktree(branch, worktree_path, start_point)?;
self.hook_executor
.execute_hooks(hooks, worktree_path, repo_root)?;
if zoxide_enabled {
self.zoxide_client.add(worktree_path)?;
}
Ok(worktree_path.to_path_buf())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
struct MockGitClient {
should_fail: bool,
}
impl MockGitClient {
fn new() -> Self {
Self { should_fail: false }
}
fn with_failure() -> Self {
Self { should_fail: true }
}
}
impl GitClient for MockGitClient {
fn create_worktree(
&self,
_branch: &str,
_path: &Path,
_start_point: Option<&str>,
) -> Result<()> {
if self.should_fail {
anyhow::bail!("Mock git failure");
}
Ok(())
}
fn list_worktrees(&self) -> Result<String> {
Ok(String::new())
}
fn remove_worktree(&self, _path: &Path) -> Result<()> {
Ok(())
}
fn remove_branch(&self, _branch: &str) -> Result<()> {
Ok(())
}
}
struct MockHookExecutor {
should_fail: bool,
}
impl MockHookExecutor {
fn new() -> Self {
Self { should_fail: false }
}
fn with_failure() -> Self {
Self { should_fail: true }
}
}
impl HookExecutor for MockHookExecutor {
fn execute_hooks(
&self,
_actions: &HookActions,
_worktree_path: &Path,
_source_path: &Path,
) -> Result<()> {
if self.should_fail {
anyhow::bail!("Mock hook failure");
}
Ok(())
}
}
struct MockZoxideClient {
should_fail: bool,
}
impl MockZoxideClient {
fn new() -> Self {
Self { should_fail: false }
}
fn with_failure() -> Self {
Self { should_fail: true }
}
}
impl ZoxideClient for MockZoxideClient {
fn add(&self, _path: &Path) -> Result<()> {
if self.should_fail {
anyhow::bail!("Mock zoxide failure");
}
Ok(())
}
}
#[test]
fn test_create_worktree_success() {
let service = WorktreeService::new(
MockGitClient::new(),
MockHookExecutor::new(),
MockZoxideClient::new(),
);
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result = service.create_worktree(
branch,
&worktree_path,
None,
&repo_root,
&hooks,
true, );
assert!(result.is_ok());
assert_eq!(result.unwrap(), worktree_path);
}
#[test]
fn test_create_worktree_with_start_point() {
let service = WorktreeService::new(
MockGitClient::new(),
MockHookExecutor::new(),
MockZoxideClient::new(),
);
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result = service.create_worktree(
branch,
&worktree_path,
Some("main"),
&repo_root,
&hooks,
false, );
assert!(result.is_ok());
assert_eq!(result.unwrap(), worktree_path);
}
#[test]
fn test_create_worktree_git_failure() {
let service = WorktreeService::new(
MockGitClient::with_failure(),
MockHookExecutor::new(),
MockZoxideClient::new(),
);
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result =
service.create_worktree(branch, &worktree_path, None, &repo_root, &hooks, true);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Mock git failure"));
}
#[test]
fn test_create_worktree_hook_failure() {
let service = WorktreeService::new(
MockGitClient::new(),
MockHookExecutor::with_failure(),
MockZoxideClient::new(),
);
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result =
service.create_worktree(branch, &worktree_path, None, &repo_root, &hooks, true);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Mock hook failure"));
}
#[test]
fn test_create_worktree_zoxide_failure() {
let service = WorktreeService::new(
MockGitClient::new(),
MockHookExecutor::new(),
MockZoxideClient::with_failure(),
);
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result = service.create_worktree(
branch,
&worktree_path,
None,
&repo_root,
&hooks,
true, );
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Mock zoxide failure"));
}
#[test]
fn test_create_worktree_zoxide_disabled() {
let service = WorktreeService::new(
MockGitClient::new(),
MockHookExecutor::new(),
MockZoxideClient::with_failure(), );
let branch = "feature";
let worktree_path = PathBuf::from("/test/worktree");
let repo_root = PathBuf::from("/test/repo");
let hooks = HookActions::default();
let result = service.create_worktree(
branch,
&worktree_path,
None,
&repo_root,
&hooks,
false, );
assert!(result.is_ok());
assert_eq!(result.unwrap(), worktree_path);
}
}