//! Integration tests for the `recent` command. use std::fs; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use assert_cmd::Command; use filetime::{FileTime, set_file_mtime}; use predicates::prelude::*; use recent::{KindFilter, RecentOptions, collect_recent}; use tempfile::tempdir; fn cargo_command() -> Command { Command::cargo_bin("recent").expect("binary") } fn pwsh_command(script: impl AsRef) -> Command { let mut command = Command::new("pwsh"); command .arg("-NoProfile") .arg("-Command") .arg(script.as_ref()); command } fn ps_quote(value: impl std::fmt::Display) -> String { format!("'{}'", value.to_string().replace('\'', "''")) } fn touch(path: &std::path::Path, age: Duration) { fs::write(path, path.display().to_string()).expect("write fixture"); let timestamp = FileTime::from_system_time(SystemTime::now() - age); set_file_mtime(path, timestamp).expect("set mtime"); } fn file_recent_options(root: PathBuf) -> RecentOptions { RecentOptions { roots: vec![root], since: None, exts: Vec::new(), name_pattern: None, kind: KindFilter::File, limit: None, } } #[test] fn collect_recent_respects_gitignore() { let root = tempdir().expect("tempdir"); fs::write(root.path().join(".gitignore"), "ignored.log\n").expect("gitignore"); touch(&root.path().join("visible.log"), Duration::from_secs(30)); touch(&root.path().join("ignored.log"), Duration::from_secs(10)); let entries = collect_recent( &file_recent_options(root.path().to_path_buf()), SystemTime::now(), ) .expect("recent entries"); assert_eq!(entries.len(), 1); assert!(entries[0].path.ends_with("visible.log")); } #[test] fn filters_since_and_extension_in_json_output() { let root = tempdir().expect("tempdir"); touch( &root.path().join("old.rs"), Duration::from_secs(60 * 60 * 24), ); touch(&root.path().join("new.rs"), Duration::from_secs(30)); touch(&root.path().join("new.txt"), Duration::from_secs(30)); let mut command = cargo_command(); command .arg("--root") .arg(root.path()) .arg("--since") .arg("1h") .arg("--ext") .arg("rs") .arg("--json") .assert() .success() .stdout(predicate::str::contains("\"path\"").and(predicate::str::contains("new.rs"))) .stdout(predicate::str::contains("old.rs").not()) .stdout(predicate::str::contains("new.txt").not()); } #[test] fn supports_powershell_root_pipeline() { let root = tempdir().expect("tempdir"); touch(&root.path().join("one.rs"), Duration::from_secs(10)); touch(&root.path().join("two.rs"), Duration::from_secs(20)); let binary = assert_cmd::cargo::cargo_bin("recent"); let script = format!( "{} | & {} --limit 1", ps_quote(root.path().display()), ps_quote(binary.display()) ); let mut command = pwsh_command(script); command .assert() .success() .stdout(predicate::str::contains("one.rs")); } #[test] fn filters_by_basename_regex() { let root = tempdir().expect("tempdir"); touch(&root.path().join("alpha.rs"), Duration::from_secs(10)); touch(&root.path().join("beta.rs"), Duration::from_secs(20)); touch(&root.path().join("gamma.txt"), Duration::from_secs(5)); let mut command = cargo_command(); command .arg("--root") .arg(root.path()) .arg("--name") .arg("^(alpha|gamma)$") .assert() .success() .stdout(predicate::str::contains("alpha.rs")) .stdout(predicate::str::contains("gamma.txt")) .stdout(predicate::str::contains("beta.rs").not()) .stdout(predicate::str::contains("delta").not()); } #[test] fn help_includes_name_filter_examples() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("--name")) .stdout(predicate::str::contains( "recent --root . --since 2h --ext rs --name", )) .stdout(predicate::str::contains("ConvertFrom-Json")); } #[test] fn basename_filter_keeps_directory_suffixes() { let root = tempdir().expect("tempdir"); let dotted_dir = root.path().join("plugins.v1"); fs::create_dir_all(&dotted_dir).expect("directory"); let mut command = cargo_command(); command .arg("--root") .arg(root.path()) .arg("--kind") .arg("dir") .arg("--name") .arg("^plugins\\.v1$") .assert() .success() .stdout(predicate::str::contains("plugins.v1")); } #[test] fn text_mode_emits_no_matches_hint() { let root = tempdir().expect("tempdir"); touch(&root.path().join("alpha.rs"), Duration::from_secs(10)); let mut command = cargo_command(); command .arg("--root") .arg(root.path()) .arg("--name") .arg("^beta$") .assert() .code(1) .stdout(predicate::str::contains("no_matches=true")); }