use super::*;
#[test]
fn segment_with_only_prefix_no_command_is_empty() {
assert!(paths("sudo").is_empty());
assert!(paths("env FOO=1").is_empty());
}
#[test]
fn compound_command_across_operators() {
let got = paths("mkdir -p x && tee y < z | sed -i s/a/b/ w");
assert!(got.contains(&("x".to_string(), "mkdir")), "got: {got:?}");
assert!(got.contains(&("y".to_string(), "tee")), "got: {got:?}");
assert!(got.contains(&("w".to_string(), "sed-i")), "got: {got:?}");
assert!(
!got.iter()
.any(|(p, _)| p == "z" || p == "<" || p == "s/a/b/"),
"input-redirect file / operator / sed-script leaked: {got:?}"
);
}
#[test]
fn no_mutation_commands_return_empty() {
assert!(paths("ls -la").is_empty());
assert!(paths("cat file.txt").is_empty());
assert!(paths("grep -r foo .").is_empty());
assert!(paths("echo hello").is_empty());
}
#[test]
fn flags_and_bare_dash_and_assignments_skipped_as_paths() {
assert!(paths("rm -rf -").is_empty());
assert!(paths("touch FOO=bar").is_empty());
}
#[test]
fn glob_operand_kept_verbatim() {
assert_eq!(paths("rm *.tmp"), vec![("*.tmp".to_string(), "rm")]);
}
#[test]
fn empty_command_is_empty() {
assert!(paths("").is_empty());
assert!(paths(" ").is_empty());
assert!(paths(" && || ; |").is_empty());
}
#[test]
fn is_assignment_recognizes_and_rejects() {
assert!(is_assignment("FOO=bar"));
assert!(is_assignment("A_B=1"));
assert!(!is_assignment("-flag"));
assert!(!is_assignment("=noname"));
assert!(!is_assignment("plain"));
}
#[test]
fn unresolved_var_verb_operand_is_dropped() {
assert!(paths("touch $TMPFILE").is_empty());
assert!(paths("cp src $DEST/out").is_empty());
assert!(paths("mkdir -p $WORK/sub").is_empty());
}
#[test]
fn unresolved_var_flag_and_dd_dropped() {
assert!(paths("pytest --junit-xml=$REPORT").is_empty());
assert!(paths("curl URL -o $OUT").is_empty());
assert!(paths("dd if=/dev/zero of=$IMG").is_empty());
}
#[test]
fn flag_output_glob_is_dropped() {
assert!(paths("tool --output=/tmp/*.json").is_empty());
assert!(paths("dd if=x of=/tmp/?.bin").is_empty());
}
#[test]
fn concrete_path_helper_rules() {
assert_eq!(concrete_path("/tmp/x.json").as_deref(), Some("/tmp/x.json"));
assert!(concrete_path("/tmp/*.json").is_none());
assert!(concrete_path("/tmp/$v").is_none());
assert!(concrete_path("-flag").is_none());
}
#[test]
fn strip_fd_qualifier_rules() {
assert_eq!(strip_fd_qualifier("2>"), ">");
assert_eq!(strip_fd_qualifier("1>>"), ">>");
assert_eq!(strip_fd_qualifier("&>"), ">");
assert_eq!(strip_fd_qualifier("12>file"), ">file");
assert_eq!(strip_fd_qualifier("&1"), "&1");
assert_eq!(strip_fd_qualifier("2"), "2");
assert_eq!(strip_fd_qualifier(">"), ">");
assert_eq!(strip_fd_qualifier("plain"), "plain");
}
#[test]
fn previously_caught_idioms_all_still_caught() {
assert_eq!(just_paths("echo hi > /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("echo hi >> /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("cmd | tee /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("touch /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("mkdir -p /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("cp src /tmp/x"), vec!["/tmp/x"]);
assert_eq!(just_paths("mv src /tmp/x"), vec!["/tmp/x", "src"]); assert_eq!(just_paths("sed -i s/a/b/ /tmp/x"), vec!["/tmp/x"]);
}
#[test]
fn trim_structural_tail_and_syntax_noise_helpers() {
assert_eq!(trim_structural_tail("/dev/null)"), "/dev/null");
assert_eq!(trim_structural_tail("/tmp/x;"), "/tmp/x");
assert_eq!(trim_structural_tail("/tmp/x))"), "/tmp/x");
assert_eq!(trim_structural_tail("/tmp/(x)"), "/tmp/(x)");
assert!(has_syntax_noise("'/tmp/x")); assert!(has_syntax_noise("\"/tmp/x")); assert!(has_syntax_noise(">(tee")); assert!(has_syntax_noise("<(cat")); assert!(has_syntax_noise("(subshell")); assert!(has_syntax_noise("/a>b")); assert!(has_syntax_noise("/a<b")); assert!(has_syntax_noise("/a\\b")); assert!(has_syntax_noise("a|b")); assert!(has_syntax_noise("a^b")); assert!(has_syntax_noise("/tmp/[unbalanced")); assert!(has_syntax_noise("/tmp/{unbalanced")); assert!(has_syntax_noise("=value")); assert!(has_syntax_noise("12345")); assert!(has_syntax_noise("a,b")); assert!(has_syntax_noise("label:")); assert!(!has_syntax_noise("/tmp/clean.txt"));
assert!(!has_syntax_noise("*.tmp")); assert!(!has_syntax_noise("src/main.rs")); assert!(!has_syntax_noise("")); }
#[test]
fn residual_noise_classes_rejected_real_relpaths_kept() {
assert!(has_syntax_noise("=1.94"));
assert!(has_syntax_noise("=2980"));
assert!(has_syntax_noise("="));
assert!(has_syntax_noise("7000"));
assert!(has_syntax_noise("0"));
assert!(has_syntax_noise("o.get('x',0):")); assert!(has_syntax_noise("turn,t.get")); assert!(has_syntax_noise("label:")); assert!(!has_syntax_noise("src/main.rs"));
assert!(!has_syntax_noise("Cargo.toml"));
assert!(!has_syntax_noise("paper.pdf"));
assert!(!has_syntax_noise("err.log"));
assert!(!has_syntax_noise("build_turns_fixture.py"));
assert!(!has_syntax_noise("/tmp/a:b"));
}
#[test]
fn version_compare_and_numeric_args_do_not_become_files() {
assert!(just_paths("python -c 'x' > =1.94").is_empty());
assert_eq!(
just_paths("echo x > /tmp/ok.txt"),
vec!["/tmp/ok.txt".to_string()]
);
}
#[test]
fn path_operand_post_trim_rejections() {
assert_eq!(path_operand("2>/dev/null)"), None); assert_eq!(path_operand("/dev/stderr)"), None);
assert_eq!(path_operand("&1"), None); assert_eq!(path_operand("'/tmp/x"), None); assert_eq!(path_operand("/tmp/clean;").as_deref(), Some("/tmp/clean"));
assert_eq!(path_operand("-"), None);
assert_eq!(path_operand(""), None);
}
#[test]
fn trim_structural_tail_brace_and_balanced_cases() {
assert_eq!(trim_structural_tail("/tmp/x}"), "/tmp/x");
assert_eq!(trim_structural_tail("/tmp/{a}"), "/tmp/{a}");
assert_eq!(trim_structural_tail("/tmp/x;}"), "/tmp/x");
assert_eq!(trim_structural_tail("/tmp/clean"), "/tmp/clean");
}
#[test]
fn tilde_home_path_is_kept_verbatim_and_unresolved() {
assert_eq!(
paths("echo x > ~/notes.txt"),
vec![("~/notes.txt".to_string(), ">")]
);
assert_eq!(
paths("cp a.txt ~/dest.txt"),
vec![("~/dest.txt".to_string(), "cp")]
);
assert_eq!(
paths("dd if=/dev/zero of=~/img.bin"),
vec![("~/img.bin".to_string(), "dd")]
);
let m = parse_bash_mutations("touch ~/scratch");
assert_eq!(m.len(), 1);
assert_eq!(
m[0].resolve(Some("/base")),
("~/scratch".to_string(), Resolution::Unresolved)
);
assert_eq!(
paths("rm /tmp/file~"),
vec![("/tmp/file~".to_string(), "rm")]
);
}