use std::fs; use std::os::unix::fs::PermissionsExt; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; use inshellah::parsers::manpage::{ManpageEntry, ManpageResult, ManpageSubcommand, OwnedSwitch}; use inshellah::store::write_result; fn unique_temp_dir(name: &str) -> std::path::PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time") .as_nanos(); std::env::temp_dir().join(format!("{name}-{}-{nanos}", std::process::id())) } #[test] fn complete_scrapes_missing_subcommand_when_parent_is_cached() { let root = unique_temp_dir("inshellah-runtime-complete"); let bin_dir = root.join("bin"); let cache_dir = root.join("cache"); fs::create_dir_all(&bin_dir).expect("bin dir"); fs::create_dir_all(&cache_dir).expect("cache dir"); let fakecmd = bin_dir.join("fakecmd"); fs::write( &fakecmd, r#"#!/bin/sh if [ "$1" = "clone" ]; then if [ "$2" = "--help" ] || [ "$2" = "-h" ]; then cat <<'EOF' Usage: fakecmd clone [OPTIONS] [directory] Options: --depth clone depth -v, --verbose verbose EOF exit 0 fi fi if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then cat <<'EOF' Usage: fakecmd [OPTIONS] COMMAND Commands: clone Clone a repository Options: -h, --help show help EOF exit 0 fi exit 2 "#, ) .expect("write fakecmd"); let mut perms = fs::metadata(&fakecmd).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(&fakecmd, perms).expect("chmod"); let parent = ManpageResult { entries: Vec::new(), subcommands: vec![ManpageSubcommand { name: "clone".to_string(), desc: "Clone a repository".to_string(), }], positionals: Vec::new(), description: String::new(), }; write_result(&cache_dir, "fakecmd", "help", &parent).expect("parent cache"); let old_path = std::env::var_os("PATH").unwrap_or_default(); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("--timeout-ms") .arg("1000") .arg("fakecmd") .arg("clone") .arg("--") .env( "PATH", format!("{}:{}", bin_dir.display(), old_path.to_string_lossy()), ) .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); assert!(stdout.contains("--depth"), "stdout = {stdout}"); assert!( cache_dir.join("fakecmd_clone.json").is_file(), "subcommand cache was not written" ); let _ = fs::remove_dir_all(root); } #[test] fn complete_does_not_scan_path_at_command_position() { let root = unique_temp_dir("inshellah-command-position-complete"); let bin_dir = root.join("bin"); let cache_dir = root.join("cache"); fs::create_dir_all(&bin_dir).expect("bin dir"); fs::create_dir_all(&cache_dir).expect("cache dir"); let fake_git = bin_dir.join("git"); fs::write(&fake_git, "#!/bin/sh\nexit 0\n").expect("write fake git"); let mut perms = fs::metadata(&fake_git).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(&fake_git, perms).expect("chmod"); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("gi") .env("PATH", &bin_dir) .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); assert_eq!(stdout.trim(), "null", "stdout = {stdout}"); let _ = fs::remove_dir_all(root); } #[test] fn complete_uses_boundary_aware_fuzzy_ranking() { let root = unique_temp_dir("inshellah-fuzzy-complete"); let cache_dir = root.join("cache"); fs::create_dir_all(&cache_dir).expect("cache dir"); let result = ManpageResult { entries: Vec::new(), subcommands: vec![ ManpageSubcommand { name: "load".to_string(), desc: "load something".to_string(), }, ManpageSubcommand { name: "clone".to_string(), desc: "clone something".to_string(), }, ], positionals: Vec::new(), description: String::new(), }; write_result(&cache_dir, "demo", "help", &result).expect("cache"); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("demo") .arg("lo") .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); let load_pos = stdout.find(r#""value":"load""#).unwrap_or(usize::MAX); let clone_pos = stdout.find(r#""value":"clone""#).unwrap_or(usize::MAX); assert!( load_pos < clone_pos, "expected boundary match to outrank substring match, stdout = {stdout}" ); let _ = fs::remove_dir_all(root); } #[test] fn complete_returns_flags_only_after_hyphen() { let root = unique_temp_dir("inshellah-flag-prefix-complete"); let cache_dir = root.join("cache"); fs::create_dir_all(&cache_dir).expect("cache dir"); let result = ManpageResult { entries: vec![ManpageEntry { switch: OwnedSwitch::Long("verbose".to_string()), param: None, desc: "verbose output".to_string(), }], subcommands: Vec::new(), positionals: Vec::new(), description: String::new(), }; write_result(&cache_dir, "demo", "help", &result).expect("cache"); let argument_output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("demo") .arg("") .output() .expect("run inshellah complete"); assert!( argument_output.status.success(), "stderr = {}", String::from_utf8_lossy(&argument_output.stderr) ); let argument_stdout = String::from_utf8(argument_output.stdout).expect("stdout"); assert_eq!(argument_stdout.trim(), "null", "stdout = {argument_stdout}"); let flag_output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("demo") .arg("--") .output() .expect("run inshellah complete"); assert!( flag_output.status.success(), "stderr = {}", String::from_utf8_lossy(&flag_output.stderr) ); let flag_stdout = String::from_utf8(flag_output.stdout).expect("stdout"); assert!( flag_stdout.contains(r#""value":"--verbose""#), "stdout = {flag_stdout}" ); let _ = fs::remove_dir_all(root); } #[test] fn complete_resolves_absolute_path_after_elevation_wrapper() { let root = unique_temp_dir("inshellah-absolute-elevation-complete"); let bin_dir = root.join("bin"); let cache_dir = root.join("cache"); fs::create_dir_all(&bin_dir).expect("bin dir"); fs::create_dir_all(&cache_dir).expect("cache dir"); let fakecmd = bin_dir.join("fakecmd"); fs::write( &fakecmd, r#"#!/bin/sh if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then printf '%s\n' 'Usage: fakecmd [OPTIONS]' '' 'Options:' ' --verbose verbose output' exit 0 fi exit 2 "#, ) .expect("write fakecmd"); let mut perms = fs::metadata(&fakecmd).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(&fakecmd, perms).expect("chmod"); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("--timeout-ms") .arg("1000") .arg("sudo") .arg(&fakecmd) .arg("--") .env("PATH", "") .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); assert!( stdout.contains(r#""value":"--verbose""#), "stdout = {stdout}" ); let _ = fs::remove_dir_all(root); }