tests/polish_batch.rs 6.2 KiB 192 lines Raw
1 //! f0rge #68: small robustness/consistency batch.2 //! - `hu13 build .` names the repo after its directory, not "repo"3 //! - gitlinks (mode 160000) get no raw copy / file page4 //! - `inbox list` escapes a hostile id5 //! - `.forge` symlinks are skipped like wiki/artifact symlinks6 //! - wiki root pages do not repeat their filename after the title7 8 use std::path::{Path, PathBuf};9 use std::process::Command;10 11 fn fresh(prefix: &str) -> PathBuf {12     let dir = std::env::temp_dir().join(format!(13         "hu13-polish-{prefix}-{}-{}",14         std::process::id(),15         std::time::SystemTime::now()16             .duration_since(std::time::UNIX_EPOCH)17             .map(|d| d.as_nanos())18             .unwrap_or(0)19     ));20     std::fs::create_dir_all(&dir).unwrap();21     dir22 }23 24 fn git(repo: &Path, args: &[&str]) -> String {25     let out = Command::new("git")26         .arg("-C")27         .arg(repo)28         .args(args)29         .output()30         .expect("spawn git");31     assert!(32         out.status.success(),33         "git {args:?}: {}",34         String::from_utf8_lossy(&out.stderr)35     );36     String::from_utf8_lossy(&out.stdout).trim().to_string()37 }38 39 fn init(repo: &Path) {40     git(repo, &["init", "-b", "main"]);41     git(repo, &["config", "user.name", "hu13-test"]);42     git(repo, &["config", "user.email", "[email protected]"]);43     git(repo, &["config", "commit.gpgsign", "false"]);44 }45 46 fn build(repo: &Path, out: &Path, cwd: Option<&Path>) -> std::process::Output {47     let mut cmd = Command::new(env!("CARGO_BIN_EXE_hu13"));48     cmd.arg("build").arg(repo).arg("-o").arg(out);49     if let Some(dir) = cwd {50         cmd.current_dir(dir);51     }52     cmd.output().expect("spawn hu13 build")53 }54 55 #[test]56 fn dot_build_uses_directory_name() {57     let parent = fresh("dot");58     let repo = parent.join("my-project");59     std::fs::create_dir_all(&repo).unwrap();60     init(&repo);61     std::fs::write(repo.join("README.md"), "# dot\n").unwrap();62     git(&repo, &["add", "-A"]);63     git(&repo, &["commit", "-m", "one"]);64     let out = fresh("out");65     let res = build(Path::new("."), &out, Some(&repo));66     assert!(67         res.status.success(),68         "{}",69         String::from_utf8_lossy(&res.stderr)70     );71     let index = std::fs::read_to_string(out.join("index.html")).unwrap();72     assert!(73         index.contains("<h1>my-project</h1>"),74         "repo named after the directory: {}",75         &index[..600.min(index.len())]76     );77     assert!(!index.contains("<h1>repo</h1>"), "not the literal 'repo'");78 }79 80 #[test]81 fn gitlink_gets_no_raw_or_page() {82     let repo = fresh("gitlink");83     init(&repo);84     std::fs::write(repo.join("README.md"), "# gitlink\n").unwrap();85     git(&repo, &["add", "-A"]);86     git(&repo, &["commit", "-m", "one"]);87     let head = git(&repo, &["rev-parse", "HEAD"]);88     git(89         &repo,90         &[91             "update-index",92             "--add",93             "--cacheinfo",94             &format!("160000,{head},sub"),95         ],96     );97     // Entry name for --cacheinfo is the last argument.98     git(&repo, &["commit", "-m", "add gitlink"]);99     let out = fresh("out");100     let res = build(&repo, &out, None);101     assert!(102         res.status.success(),103         "{}",104         String::from_utf8_lossy(&res.stderr)105     );106     assert!(!out.join("raw/sub").exists(), "no raw copy for a gitlink");107     assert!(108         !out.join("files/sub.html").exists(),109         "no file page for a gitlink"110     );111     let index = std::fs::read_to_string(out.join("index.html")).unwrap();112     assert!(index.contains("@ submodule"), "gitlink marked in the table");113 }114 115 #[test]116 fn inbox_list_escapes_hostile_id() {117     let inbox = fresh("inbox");118     // A filename can contain ESC; list must never print it raw.119     let name = "bad\u{1b}id.json";120     std::fs::write(121         inbox.join(name),122         r#"{"type":"issue","title":"t","received_at":"2026-09-10T00:00:00Z"}"#,123     )124     .unwrap();125     let out = Command::new(env!("CARGO_BIN_EXE_hu13"))126         .arg("inbox")127         .arg("list")128         .arg("--inbox-dir")129         .arg(&inbox)130         .output()131         .unwrap();132     assert!(out.status.success());133     let stdout = String::from_utf8_lossy(&out.stdout);134     assert!(stdout.contains("\\x1b"), "escaped id: {stdout}");135     assert!(!stdout.contains('\u{1b}'), "no raw ESC: {stdout}");136 }137 138 #[test]139 fn forge_symlink_is_skipped() {140     let repo = fresh("symlink");141     init(&repo);142     std::fs::write(repo.join("README.md"), "# symlink\n").unwrap();143     let dir = repo.join(".forge/issues/aaaaaaaa-1111-4111-8111-111111111111");144     std::fs::create_dir_all(&dir).unwrap();145     let outside = fresh("outside").join("issue.md");146     std::fs::write(147         &outside,148         "---\nid: aaaaaaaa-1111-4111-8111-111111111111\ntitle: linked\nstate: open\ncreated: 2026-09-10T00:00:00Z\n---\nbody\n",149     )150     .unwrap();151     std::os::unix::fs::symlink(&outside, dir.join("issue.md")).unwrap();152     git(&repo, &["add", "-A"]);153     git(&repo, &["commit", "-m", "one"]);154     let out = fresh("out");155     let res = build(&repo, &out, None);156     assert!(157         res.status.success(),158         "{}",159         String::from_utf8_lossy(&res.stderr)160     );161     let err = String::from_utf8_lossy(&res.stderr);162     assert!(err.contains("symlink"), "warned about the symlink: {err}");163     assert!(164         !out.join("issues/aaaaaaaa-1111-4111-8111-111111111111.html")165             .exists(),166         "no page for a symlinked issue"167     );168 }169 170 #[test]171 fn wiki_root_page_does_not_repeat_its_filename() {172     let repo = fresh("wiki");173     init(&repo);174     std::fs::write(repo.join("README.md"), "# wiki\n").unwrap();175     std::fs::create_dir_all(repo.join(".forge/wiki/nested")).unwrap();176     std::fs::write(repo.join(".forge/wiki/README.md"), "# hu13 README\n").unwrap();177     std::fs::write(repo.join(".forge/wiki/nested/guide.md"), "# Guide\n").unwrap();178     git(&repo, &["add", "-A"]);179     git(&repo, &["commit", "-m", "one"]);180     let out = fresh("out");181     let res = build(&repo, &out, None);182     assert!(res.status.success());183     let index = std::fs::read_to_string(out.join("wiki/index.html")).unwrap();184     assert!(185         !index.contains("hu13 README</a> README"),186         "root page path hint dropped: {index}"187     );188     assert!(189         index.contains("Guide</a> <span class=\"muted\">nested/guide.md</span>"),190         "nested pages keep the path hint"191     );192 }