use super::expand_template;
use crate::testing::TestRepo;
use std::collections::HashMap;
fn test_repo() -> TestRepo {
TestRepo::new()
}
fn vars_with_branch(branch: &str) -> HashMap<&str, &str> {
let mut vars = HashMap::new();
vars.insert("branch", branch);
vars.insert("main_worktree", "myrepo");
vars.insert("repo", "myrepo");
vars
}
#[test]
fn test_expand_template_normal() {
let test = test_repo();
let vars = vars_with_branch("feature");
let result = expand_template(
"echo {{ branch }} {{ main_worktree }}",
&vars,
true,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "echo feature myrepo");
}
#[test]
fn test_expand_template_branch_with_slashes() {
let test = test_repo();
let vars = vars_with_branch("feature/nested/branch");
let result = expand_template(
"echo {{ branch | sanitize }}",
&vars,
true,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "echo feature-nested-branch");
}
#[test]
fn test_expand_template_branch_raw_with_slashes() {
let test = test_repo();
let vars = vars_with_branch("feature/nested/branch");
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
assert_eq!(result, "echo feature/nested/branch");
}
#[test]
#[cfg(unix)]
fn test_expand_template_branch_escaping() {
let test = test_repo();
let expand = |input| {
expand_template(
"echo {{ branch }}",
&vars_with_branch(input),
true,
&test.repo,
"test",
)
.unwrap()
};
assert_eq!(expand("feature name"), "echo 'feature name'"); assert_eq!(expand("feature$(whoami)"), "echo 'feature$(whoami)'"); assert_eq!(expand("feature`id`"), "echo 'feature`id`'"); }
#[test]
#[cfg(unix)]
fn snapshot_expand_template_branch_with_quotes() {
let test = test_repo();
let vars = vars_with_branch("feature'test");
let result = expand_template("echo '{{ branch }}'", &vars, true, &test.repo, "test").unwrap();
insta::assert_snapshot!(result, @r"echo ''feature'\''test''");
}
#[test]
#[cfg(unix)]
fn test_expand_template_extra_vars_path_escaping() {
let test = test_repo();
let expand = |path| {
expand_template(
"cd {{ worktree }}",
&HashMap::from([("worktree", path)]),
true,
&test.repo,
"test",
)
.unwrap()
};
assert_eq!(
expand("/path with spaces/to/wt"),
"cd '/path with spaces/to/wt'"
); assert_eq!(expand("/path/$USER/worktree"), "cd '/path/$USER/worktree'"); }
#[test]
#[cfg(unix)]
fn snapshot_expand_template_extra_vars_with_command_substitution() {
let test = test_repo();
let mut extras = HashMap::new();
extras.insert("target", "main; rm -rf /");
let result =
expand_template("git merge {{ target }}", &extras, true, &test.repo, "test").unwrap();
insta::assert_snapshot!(result, @"git merge 'main; rm -rf /'");
}
#[test]
fn test_expand_template_variable_override() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("branch", "overridden");
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
assert_eq!(result, "echo overridden");
}
#[test]
fn test_expand_template_missing_variable() {
let test = test_repo();
let vars: HashMap<&str, &str> = HashMap::new();
let result = expand_template("echo {{ undefined }}", &vars, true, &test.repo, "test");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.message.contains("undefined value"),
"got: {}",
err.message
);
}
#[test]
#[cfg(unix)]
fn test_expand_template_empty_branch() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("branch", "");
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
assert_eq!(result, "echo ''");
}
#[test]
#[cfg(unix)]
fn test_expand_template_unicode_in_branch() {
let test = test_repo();
let vars = vars_with_branch("feature-\u{1F680}");
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
assert_eq!(result, "echo 'feature-\u{1F680}'");
}
#[test]
fn test_expand_template_backslash_in_branch() {
let test = test_repo();
let vars = vars_with_branch("feature\\branch");
let result = expand_template(
"path/{{ branch | sanitize }}",
&vars,
false,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "path/feature-branch");
}
#[test]
fn test_expand_template_multiple_replacements() {
let test = test_repo();
let mut vars = vars_with_branch("feature");
vars.insert("worktree", "/path/to/wt");
vars.insert("target", "develop");
let result = expand_template(
"cd {{ worktree }} && git merge {{ target }} from {{ branch }}",
&vars,
true,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "cd /path/to/wt && git merge develop from feature");
}
#[test]
fn test_expand_template_curly_braces_without_variables() {
let test = test_repo();
let vars: HashMap<&str, &str> = HashMap::new();
let result = expand_template("echo {}", &vars, true, &test.repo, "test").unwrap();
assert_eq!(result, "echo {}");
}
#[test]
fn test_expand_template_nested_curly_braces() {
let test = test_repo();
let vars = vars_with_branch("main");
let result = expand_template(
"echo {{ '{' ~ branch ~ '}' }}",
&vars,
true,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "echo '{main}'");
}
#[test]
#[cfg(unix)]
fn snapshot_shell_escaping_special_chars() {
let test_cases = vec![
("spaces", "feature name"),
("dollar", "feature$USER"),
("command_sub", "feature$(whoami)"),
("backticks", "feature`id`"),
("semicolon", "feature;rm -rf /"),
("pipe", "feature|grep foo"),
("ampersand", "feature&background"),
("redirect", "feature>output.txt"),
("wildcard", "feature*glob"),
("question", "feature?char"),
("brackets", "feature[0-9]"),
];
let test = test_repo();
let mut results = Vec::new();
for (name, branch) in test_cases {
let vars = vars_with_branch(branch);
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
results.push((name, branch, result));
}
insta::assert_yaml_snapshot!(results);
}
#[test]
#[cfg(unix)]
fn snapshot_shell_escaping_quotes() {
let test_cases = vec![
("single_quote", "feature'test"),
("double_quote", "feature\"test"),
("mixed_quotes", "feature'test\"mixed"),
("multiple_single", "don't'panic"),
];
let test = test_repo();
let mut results = Vec::new();
for (name, branch) in test_cases {
let vars = vars_with_branch(branch);
let result = expand_template("echo {{ branch }}", &vars, true, &test.repo, "test").unwrap();
results.push((name, branch, result));
}
insta::assert_yaml_snapshot!(results);
}
#[test]
#[cfg(unix)]
fn snapshot_shell_escaping_paths() {
let test_cases = vec![
("spaces", "/path with spaces/to/worktree"),
("dollar", "/path/$USER/worktree"),
("tilde", "~/worktree"),
("special_chars", "/path/to/worktree (new)"),
("unicode", "/path/to/\u{1F680}/worktree"),
];
let test = test_repo();
let mut results = Vec::new();
for (name, path) in test_cases {
let mut vars = vars_with_branch("main");
vars.insert("worktree", path);
let result = expand_template(
"cd {{ worktree }} && echo {{ branch }}",
&vars,
true,
&test.repo,
"test",
)
.unwrap();
results.push((name, path, result));
}
insta::assert_yaml_snapshot!(results);
}
#[test]
#[cfg(unix)]
fn snapshot_complex_templates() {
let test_cases = vec![
(
"cd_and_merge",
"cd {{ worktree }} && git merge {{ target }}",
"feature branch",
),
(
"npm_install",
"cd {{ main_worktree }}/{{ branch | sanitize }} && npm install",
"feature/new-ui",
),
(
"echo_vars",
"echo 'Branch: {{ branch }}' 'Worktree: {{ worktree }}'",
"test$injection",
),
];
let test = test_repo();
let mut results = Vec::new();
for (name, template, branch) in test_cases {
let mut vars = HashMap::new();
vars.insert("branch", branch);
vars.insert("main_worktree", "/repo/path");
vars.insert("worktree", "/path with spaces/wt");
vars.insert("target", "main; rm -rf /");
let result = expand_template(template, &vars, true, &test.repo, "test").unwrap();
results.push((name, template, branch, result));
}
insta::assert_yaml_snapshot!(results);
}
#[test]
fn test_expand_template_literal_normal() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("main_worktree", "myrepo");
vars.insert("branch", "feature");
let result = expand_template(
"{{ main_worktree }}.{{ branch }}",
&vars,
false,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "myrepo.feature");
}
#[test]
fn test_expand_template_literal_unicode_no_escaping() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("main_worktree", "myrepo");
vars.insert("branch", "test-\u{2282}");
let result = expand_template(
"{{ main_worktree }}.{{ branch }}",
&vars,
false,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "myrepo.test-\u{2282}");
assert!(
!result.contains('\''),
"Path should not contain shell quotes"
);
}
#[test]
fn test_expand_template_literal_spaces_no_escaping() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("main_worktree", "my repo");
vars.insert("branch", "feature name");
let result = expand_template(
"{{ main_worktree }}.{{ branch }}",
&vars,
false,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "my repo.feature name");
assert!(
!result.contains('\''),
"Path should not contain shell quotes"
);
}
#[test]
fn test_expand_template_literal_sanitizes_slashes() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("main_worktree", "myrepo");
vars.insert("branch", "feature/nested/branch");
let result = expand_template(
"{{ main_worktree }}.{{ branch | sanitize }}",
&vars,
false,
&test.repo,
"test",
)
.unwrap();
assert_eq!(result, "myrepo.feature-nested-branch");
}
#[test]
#[cfg(unix)]
fn test_expand_template_literal_vs_escaped_unicode() {
let test = test_repo();
let mut vars = HashMap::new();
vars.insert("main_worktree", "myrepo");
vars.insert("branch", "test-\u{2282}");
let template = "{{ main_worktree }}.{{ branch }}";
let literal_result = expand_template(template, &vars, false, &test.repo, "test").unwrap();
let escaped_result = expand_template(template, &vars, true, &test.repo, "test").unwrap();
assert_eq!(literal_result, "myrepo.test-\u{2282}");
assert_eq!(escaped_result, "myrepo.'test-\u{2282}'");
}