//! Projects from the command line (spec ยง23.1): any kind of work, with or //! without a folder that Spectra never reads; links drawn by the owner; an //! agent needs a registered project. use spectra_core::keys::KeyPair; use std::path::Path; use std::process::{Command, Output}; fn hermetic(dir: &Path) -> Command { let mut c = Command::new(env!("CARGO_BIN_EXE_spectra")); c.env("SPECTRA_DATA", dir.join("data")) .env("SPECTRA_HOME", dir.join("owner")) .env("HOME", dir) .env("XDG_DATA_HOME", dir.join("xdg")) .env("NO_COLOR", "1"); c } fn run(dir: &Path, args: &[&str]) -> Output { hermetic(dir).args(args).output().unwrap() } fn text(o: &Output) -> String { format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr)) } fn setup() -> tempfile::TempDir { let dir = tempfile::tempdir().unwrap(); KeyPair::create_home(&dir.path().join("owner"), "Andy McCall", "").unwrap(); assert!(run(dir.path(), &["init"]).status.success()); dir } fn projects(d: &Path) -> serde_json::Value { serde_json::from_slice(&run(d, &["project", "list", "--json"]).stdout).unwrap() } #[test] fn any_work_is_a_project_and_the_owner_links_them() { let dir = setup(); let d = dir.path(); // A book being restructured: a folder of chapters, and an index with none. std::fs::create_dir_all(d.join("Manuscript")).unwrap(); std::fs::write(d.join("Manuscript/.oxymodules"), "this is not read").unwrap(); let o = run(d, &["project", "add", d.join("Manuscript").to_str().unwrap(), "-d", "the book, chapter by chapter"]); assert!(o.status.success(), "{}", text(&o)); let o = run(d, &["project", "add", "index", "-d", "the index and cross-references", "--url", "https://example.org/index"]); assert!(o.status.success(), "{}", text(&o)); let o = run(d, &["project", "link", "index", "--depends-on", "manuscript"]); assert!(o.status.success(), "{}", text(&o)); let list = projects(d); let book = list.as_array().unwrap().iter().find(|p| p["name"] == "manuscript").unwrap(); let index = list.as_array().unwrap().iter().find(|p| p["name"] == "index").unwrap(); assert_eq!(book["description"], "the book, chapter by chapter"); assert!(book["path"].as_str().unwrap().ends_with("Manuscript")); assert!(index["path"].is_null(), "no folder"); assert_eq!(index["url"], "https://example.org/index"); assert_eq!(index["depends_on"], serde_json::json!(["manuscript"])); assert_eq!(book["depended_on_by"], serde_json::json!(["index"])); assert!(run(d, &["project", "link", "index", "--depends-on", "manuscript", "--remove"]).status.success()); assert_eq!(projects(d)[0]["depends_on"], serde_json::json!([])); assert!(!run(d, &["project", "link", "index", "--depends-on", "nowhere"]).status.success()); assert!(run(d, &["admin", "check"]).status.success()); assert!(run(d, &["admin", "verify"]).status.success()); } #[test] fn a_project_is_registered_once_and_an_agent_needs_one() { let dir = setup(); let d = dir.path(); std::fs::create_dir_all(d.join("oxygen")).unwrap(); assert!(run(d, &["project", "add", d.join("oxygen").to_str().unwrap()]).status.success()); let again = run(d, &["project", "add", d.join("oxygen").to_str().unwrap(), "--name", "other"]); assert!(!again.status.success() && text(&again).contains("already a project"), "{}", text(&again)); assert!(!run(d, &["project", "add", "Not A Name"]).status.success()); let o = run( d, &["agent", "add", "kimi", "--kind", "kimi-code", "--project", "nowhere", "--home", d.join("kimi").to_str().unwrap(), "--yes"], ); assert!(!o.status.success() && text(&o).contains("no project named nowhere"), "{}", text(&o)); assert!(!d.join("kimi").exists(), "no key is made for an agent that cannot be added"); }