//! Integration tests for the `pathshadow` command. use std::fs; use assert_cmd::Command; use pathshadow::{SourceKind, classify_source, find_command_matches}; use predicates::prelude::*; use tempfile::tempdir; fn cargo_command() -> Command { Command::cargo_bin("pathshadow").expect("binary") } fn classify_path(path: &str) -> SourceKind { classify_source(std::path::Path::new(path)) } fn demo_path_fixture() -> (tempfile::TempDir, tempfile::TempDir, String) { let first = tempdir().expect("first path"); let second = tempdir().expect("second path"); fs::write(first.path().join("demo.cmd"), "@echo first").expect("first command"); fs::write(second.path().join("demo.exe"), "binary").expect("second command"); let path_value = format!("{};{}", first.path().display(), second.path().display()); (first, second, path_value) } #[test] fn classifies_common_install_roots() { assert_eq!( classify_path(r"C:\Users\example\scoop\shims\rg.exe"), SourceKind::ScoopShim ); assert_eq!( classify_path(r"C:\Users\example\scoop\shims"), SourceKind::ScoopShim ); assert_eq!( classify_path(r"C:\Users\example\.cargo\bin\cargo.exe"), SourceKind::CargoBin ); assert_eq!( classify_path(r"C:\Users\example\.cargo\bin"), SourceKind::CargoBin ); } #[test] fn finds_shadowed_matches_in_path_order() { let (first, second, _) = demo_path_fixture(); let matches = find_command_matches( "demo", &[first.path().to_path_buf(), second.path().to_path_buf()], &[".CMD".into(), ".EXE".into()], ); assert_eq!(matches.len(), 2); assert_eq!(matches[0].path, first.path().join("demo.cmd")); assert!(matches[0].is_primary); assert!(!matches[1].is_primary); } #[test] fn reports_duplicates_from_cli() { let (_first, _second, path_value) = demo_path_fixture(); let mut command = cargo_command(); command .arg("demo") .env("PATH", path_value) .env("PATHEXT", ".CMD;.EXE") .assert() .success() .stdout(predicate::str::contains("demo")) .stdout(predicate::str::contains("primary")) .stdout(predicate::str::contains("shadowed")); } #[test] fn supports_powershell_pipeline() { let (_first, _second, path_value) = demo_path_fixture(); let binary = assert_cmd::cargo::cargo_bin("pathshadow"); let script = format!("'demo' | & '{}'", binary.display()); let mut command = Command::new("pwsh"); command .arg("-NoProfile") .arg("-Command") .arg(script) .env("PATH", path_value) .env("PATHEXT", ".CMD;.EXE") .assert() .success() .stdout(predicate::str::contains("demo")); } #[test] fn emits_rank_reason_and_winner_in_json_output() { let (first, _second, path_value) = demo_path_fixture(); let winner = first.path().join("demo.cmd"); let winner_json = winner.display().to_string().replace('\\', "\\\\"); let mut command = cargo_command(); command .arg("demo") .arg("--json") .env("PATH", path_value) .env("PATHEXT", ".CMD;.EXE") .assert() .success() .stdout(predicate::str::contains("\"path_rank\":0")) .stdout(predicate::str::contains("\"path_rank\":1")) .stdout(predicate::str::contains( "\"shadow_reason\":\"path_winner\"", )) .stdout(predicate::str::contains( "\"shadow_reason\":\"shadowed_by_earlier_path\"", )) .stdout(predicate::str::contains(format!( "\"winner_path\":\"{winner_json}\"" ))); } #[test] fn help_includes_path_diagnosis_examples() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("pathshadow rg")) .stdout(predicate::str::contains( "pathshadow python npm --shell powershell", )) .stdout(predicate::str::contains( "pathshadow --all-duplicates --high-signal --summary --json", )) .stdout(predicate::str::contains("ConvertFrom-Json")); } #[test] fn reports_alias_targets_for_powershell_resolution() { let mut command = cargo_command(); command .arg("where") .arg("--shell") .arg("powershell") .arg("--summary") .arg("--json") .assert() .success() .stdout(predicate::str::contains("\"shell_winner_type\":\"alias\"")) .stdout(predicate::str::contains( "\"shell_winner_target\":\"Where-Object\"", )) .stdout(predicate::str::contains("\"shell_disagrees\":true")); }