crates/cli/tests/dog_issue.rs 5.5 KiB 149 lines Raw
1 //! dog-issue acceptance: quoting round-trip, empty-label drop,2 //! unknown-field warnings, archived default-hide on temp repos.3 4 use std::path::{Path, PathBuf};5 use std::process::Command;6 7 fn bin() -> Command {8     Command::new(env!("CARGO_BIN_EXE_f0rge"))9 }10 11 fn run_in(dir: &Path, args: &[&str]) -> (String, String, i32) {12     let out = bin().current_dir(dir).args(args).output().expect("run f0rge");13     (14         String::from_utf8_lossy(&out.stdout).into_owned(),15         String::from_utf8_lossy(&out.stderr).into_owned(),16         out.status.code().unwrap_or(-1),17     )18 }19 20 fn tmp(tag: &str) -> PathBuf {21     let d = std::env::temp_dir()22         .join(format!("f0rge-dogissue-{tag}-{}-{}", std::process::id(), line!()));23     let _ = std::fs::remove_dir_all(&d);24     std::fs::create_dir_all(&d).unwrap();25     d26 }27 28 fn setup(tag: &str) -> PathBuf {29     let dir = tmp(tag);30     let (_, _, code) = run_in(&dir, &["init"]);31     assert_eq!(code, 0);32     dir33 }34 35 fn jv(s: &str) -> serde_json::Value {36     serde_json::from_str(s.trim()).expect("json line")37 }38 39 #[test]40 fn label_quote_roundtrip() {41     let dir = setup("quote");42     let (_, err, code) = run_in(43         &dir,44         &["label", "create", "--name", "123", "--color", "5319E7", "--description", "true"],45     );46     assert_eq!(code, 0, "{err}");47     let raw = std::fs::read_to_string(dir.join(".forge/labels.md")).unwrap();48     assert!(raw.contains("\"123\""), "name quoted:\n{raw}");49     assert!(raw.contains("\"5319E7\""), "color quoted:\n{raw}");50     assert!(raw.contains("\"true\""), "desc quoted:\n{raw}");51     let (o, err, code) = run_in(&dir, &["label", "list", "--json"]);52     assert_eq!(code, 0, "{err}");53     let v = jv(&o);54     assert_eq!(v["name"], "123");55     assert_eq!(v["color"], "5319E7");56     assert_eq!(v["description"], "true");57     // plain names stay bare for humans58     let (_, _, code) = run_in(&dir, &["label", "create", "--name", "bug"]);59     assert_eq!(code, 0);60     let raw = std::fs::read_to_string(dir.join(".forge/labels.md")).unwrap();61     assert!(raw.contains("- name: bug\n"), "plain stays bare:\n{raw}");62     let _ = std::fs::remove_dir_all(&dir);63 }64 65 #[test]66 fn milestone_task_quote_roundtrip() {67     let dir = setup("mtquote");68     let (_, err, code) = run_in(&dir, &["milestone", "create", "--name", "007", "--due", "2026-10-01"]);69     assert_eq!(code, 0, "{err}");70     let (o, err, code) = run_in(&dir, &["milestone", "list", "--json"]);71     assert_eq!(code, 0, "{err}");72     assert_eq!(jv(&o)["name"], "007");73     let (_, err, code) = run_in(&dir, &["task", "create", "--title", "123", "--json"]);74     assert_eq!(code, 0, "{err}");75     let (o, _, code) = run_in(&dir, &["task", "list", "--json"]);76     assert_eq!(code, 0);77     assert_eq!(jv(&o)["title"], "123");78     let _ = std::fs::remove_dir_all(&dir);79 }80 81 #[test]82 fn issue_empty_labels_dropped() {83     let dir = setup("empty");84     let (o, _, code) = run_in(85         &dir,86         &["issue", "create", "--title", "T", "--label", "", "--label", "bug", "--assignee", ""],87     );88     assert_eq!(code, 0);89     let v = jv(&o);90     assert_eq!(v["labels"], serde_json::json!(["bug"]));91     assert_eq!(v["assignees"], serde_json::json!([]));92     // empty-only verbs are usage errors, one line93     let n = v["number"].to_string();94     let (_, err, code) = run_in(&dir, &["issue", "label", &n, "--label", ""]);95     assert_eq!(code, 2);96     assert_eq!(err.lines().count(), 1);97     let (_, err, code) = run_in(&dir, &["issue", "assign", &n, "--assignee", ""]);98     assert_eq!(code, 2);99     assert_eq!(err.lines().count(), 1);100     let _ = std::fs::remove_dir_all(&dir);101 }102 103 #[test]104 fn unknown_field_warns() {105     let dir = setup("warn");106     let (o, _, code) = run_in(&dir, &["issue", "create", "--title", "T", "--json"]);107     assert_eq!(code, 0);108     let uuid = {109         let issues = dir.join(".forge/issues");110         let e = std::fs::read_dir(&issues).unwrap().flatten().next().unwrap();111         e.file_name().to_string_lossy().into_owned()112     };113     let _ = o;114     let f = dir.join(".forge/issues").join(&uuid).join("issue.md");115     let t = std::fs::read_to_string(&f).unwrap().replace("state: open", "state: open\nxtra: 1");116     std::fs::write(&f, t).unwrap();117     let (_, err, code) = run_in(&dir, &["issue", "view", &uuid]);118     assert_eq!(code, 0);119     assert!(err.contains("unknown field"), "stderr:\n{err}");120     std::fs::write(dir.join(".forge/labels.md"), "- name: x\n  bogus: 1\n").unwrap();121     let (_, err, code) = run_in(&dir, &["label", "list"]);122     assert_eq!(code, 0);123     assert!(err.contains("unknown field"), "stderr:\n{err}");124     let _ = std::fs::remove_dir_all(&dir);125 }126 127 #[test]128 fn archived_hidden_by_default() {129     let dir = setup("arch");130     let (o, _, code) = run_in(&dir, &["issue", "create", "--title", "A", "--json"]);131     assert_eq!(code, 0);132     let n = jv(&o)["number"].to_string();133     let (_, _, code) = run_in(&dir, &["issue", "close", &n]);134     assert_eq!(code, 0);135     // flip to archived on disk (no archive verb in scope)136     let issues = dir.join(".forge/issues");137     for e in std::fs::read_dir(&issues).unwrap().flatten() {138         let f = issues.join(e.file_name()).join("issue.md");139         let t = std::fs::read_to_string(&f).unwrap().replace("state: closed", "state: archived");140         std::fs::write(&f, t).unwrap();141     }142     let (o, _, code) = run_in(&dir, &["issue", "list", "--json"]);143     assert_eq!(code, 0);144     assert_eq!(o, "[]\n", "default hides archived, got:\n{o}");145     let (o, _, code) = run_in(&dir, &["issue", "list", "--json", "--state", "archived"]);146     assert_eq!(code, 0);147     assert_eq!(o.lines().count(), 1);148     let _ = std::fs::remove_dir_all(&dir);149 }