use crate::common::{
TestRepo, repo, set_temp_home_env, setup_home_snapshot_settings,
setup_snapshot_settings_with_home, temp_home, wt_command,
};
use insta_cmd::assert_cmd_snapshot;
use rstest::rstest;
use std::fs;
use tempfile::TempDir;
#[rstest]
fn test_hook_show_with_both_configs(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[pre-commit]
user-lint = "pre-commit run --all-files"
"#,
)
.unwrap();
repo.write_project_config(
r#"pre-merge = [
{build = "cargo build"},
{test = "cargo test"},
]
[post-start]
deps = "npm install"
"#,
);
repo.commit("Add project config");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook").arg("show").current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_no_hooks(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook").arg("show").current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
fn setup_all_hook_types(repo: &TestRepo, temp_home: &TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
repo.write_project_config(
r#"pre-merge = [
{build = "cargo build"},
{test = "cargo test"},
]
[post-start]
deps = "npm install"
[post-merge]
deploy = "scripts/deploy.sh"
[pre-remove]
cleanup = "echo cleanup"
[post-remove]
notify = "echo removed"
"#,
);
repo.commit("Add project config");
}
#[rstest]
fn test_hook_show_filter_by_type(repo: TestRepo, temp_home: TempDir) {
setup_all_hook_types(&repo, &temp_home);
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("pre-merge")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_filter_post_merge(repo: TestRepo, temp_home: TempDir) {
setup_all_hook_types(&repo, &temp_home);
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("post-merge")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_filter_pre_remove(repo: TestRepo, temp_home: TempDir) {
setup_all_hook_types(&repo, &temp_home);
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("pre-remove")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_filter_post_remove(repo: TestRepo, temp_home: TempDir) {
setup_all_hook_types(&repo, &temp_home);
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("post-remove")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_approval_status(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["remote", "remove", "origin"]);
let project_id_str = repo.project_id();
let canonical_home = crate::common::canonicalize(temp_home.path())
.unwrap_or_else(|_| temp_home.path().to_path_buf());
let global_config_dir = canonical_home.join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
let config_path = global_config_dir.join("config.toml");
fs::write(
&config_path,
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let approvals_path = global_config_dir.join("approvals.toml");
fs::write(
&approvals_path,
format!(
r#"[projects.'{project_id_str}']
approved-commands = ["cargo build"]
"#
),
)
.unwrap();
repo.write_project_config(
r#"pre-merge = [
{build = "cargo build"},
{test = "cargo test"},
]
"#,
);
repo.commit("Add project config");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.env("WORKTRUNK_CONFIG_PATH", &config_path);
cmd.env("WORKTRUNK_APPROVALS_PATH", &approvals_path);
cmd.arg("hook").arg("show").current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_error_with_context_formatting(temp_home: TempDir) {
let temp_dir = tempfile::tempdir().unwrap();
let settings = setup_home_snapshot_settings(&temp_home);
settings.bind(|| {
let mut cmd = wt_command();
cmd.arg("remove").current_dir(temp_dir.path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_project_config_no_hooks(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
repo.write_project_config(
r#"# Project config with no hooks
[list]
url = "http://localhost:8080"
"#,
);
repo.commit("Add project config without hooks");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook").arg("show").current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_outside_git_repo(temp_home: TempDir) {
let temp_dir = tempfile::tempdir().unwrap();
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[pre-commit]
lint = "pre-commit run"
"#,
)
.unwrap();
let mut settings = setup_home_snapshot_settings(&temp_home);
let canonical_home = crate::common::canonicalize(temp_home.path())
.unwrap_or_else(|_| temp_home.path().to_path_buf());
settings.add_filter(®ex::escape(&canonical_home.to_string_lossy()), "~");
settings.add_filter(r"thread '([^']+)' \(\d+\)", "thread '$1' ([THREAD_ID])");
settings.bind(|| {
let mut cmd = wt_command();
cmd.arg("hook").arg("show").current_dir(temp_dir.path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_clear_no_approvals(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["remote", "remove", "origin"]);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
let config_path = global_config_dir.join("config.toml");
fs::write(
&config_path,
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.env("WORKTRUNK_CONFIG_PATH", &config_path);
cmd.args(["hook", "approvals", "clear"])
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_clear_with_approvals(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["remote", "remove", "origin"]);
let project_id_str = repo.project_id();
let config_path = repo.test_approvals_path().with_file_name("config.toml");
fs::write(
&config_path,
format!(
r#"worktree-path = "../{{{{ repo }}}}.{{{{ branch }}}}"
[projects.'{project_id_str}']
approved-commands = ["cargo build", "cargo test", "npm install"]
"#
),
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.env("WORKTRUNK_CONFIG_PATH", &config_path);
cmd.args(["hook", "approvals", "clear"])
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_expanded_syntax_error(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
repo.write_project_config(
r#"[pre-commit]
broken = "echo {{ branch"
"#,
);
repo.commit("Add project config with broken template");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("pre-commit")
.arg("--expanded")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_expanded_undefined_var(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
repo.write_project_config(
r#"[pre-commit]
optional-var = "echo {{ base }}"
"#,
);
repo.commit("Add project config with optional variable");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("pre-commit")
.arg("--expanded")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_hook_show_expanded_valid_template(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
repo.write_project_config(
r#"[pre-commit]
valid = "echo branch={{ branch }} repo={{ repo }}"
"#,
);
repo.commit("Add project config with valid template");
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
cmd.arg("hook")
.arg("show")
.arg("pre-commit")
.arg("--expanded")
.current_dir(repo.root_path());
set_temp_home_env(&mut cmd, temp_home.path());
assert_cmd_snapshot!(cmd);
});
}