use super::{assert_prefixed, noop_editor, with_fx, with_lernie_home, writing_editor};
use crate::cmd::{Outcome, config, dispatch, new, prompt};
use crate::template::{GitRunner, RealGit};
use crate::workspace::{fixture, repo_git};
use std::path::Path;
use tempfile::TempDir;
fn run_new(home: &Path, dest: &Path) -> Result<Outcome, crate::cmd::Error> {
with_lernie_home(home, || {
with_fx("lernie", b"", &noop_editor, |fx| {
new::run(
new::Args {
path: Some(dest.to_path_buf()),
},
fx,
)
})
.0
})
}
fn config_tree(dest: &Path) -> String {
RealGit::new()
.run_capture(
&repo_git(dest),
&["ls-tree", "-r", "--name-only", "config/default"],
)
.unwrap()
}
#[test]
fn new_scaffolds_and_prints_the_destination() {
let home = TempDir::new().unwrap();
let tmp = TempDir::new().unwrap();
let dest = tmp.path().join("ws");
let Outcome::Line(line) = run_new(home.path(), &dest).unwrap() else {
panic!("new prints its destination")
};
assert_eq!(line, dest.display().to_string());
assert!(dest.join("repo.git").is_dir());
}
#[test]
fn new_founds_an_unseeded_root_so_the_config_commit_carries_descriptions() {
let home = TempDir::new().unwrap();
let tmp = TempDir::new().unwrap();
let dest = tmp.path().join("ws");
run_new(home.path(), &dest).unwrap();
assert!(home.path().join("tools/read_file.json").is_file());
assert!(home.path().join("skills/read_file/SKILL.md").is_file());
let tree = config_tree(&dest);
for path in [
"manifest.yaml",
"providers.yaml",
"workflow.yaml",
"version",
"descriptions/tools/read_file.json",
"descriptions/skills/read_file.md",
] {
assert!(tree.contains(path), "{path} missing from:\n{tree}");
}
}
#[test]
fn new_over_a_seeded_root_keeps_the_curated_pool_entry() {
let home = TempDir::new().unwrap();
std::fs::create_dir_all(home.path().join("tools")).unwrap();
std::fs::write(home.path().join("tools/read_file.json"), r#"{"mine":1}"#).unwrap();
let tmp = TempDir::new().unwrap();
let dest = tmp.path().join("ws");
run_new(home.path(), &dest).unwrap();
assert_eq!(
std::fs::read_to_string(home.path().join("tools/read_file.json")).unwrap(),
r#"{"mine":1}"#
);
let shown = RealGit::new()
.run_capture(
&repo_git(&dest),
&["show", "config/default:descriptions/tools/read_file.json"],
)
.unwrap();
assert_eq!(shown, r#"{"mine":1}"#);
}
#[test]
fn new_reports_a_scaffold_failure() {
let home = TempDir::new().unwrap();
let tmp = TempDir::new().unwrap();
let dest = tmp.path().join("ws");
std::fs::create_dir_all(&dest).unwrap();
std::fs::write(dest.join("occupied"), b"x").unwrap();
assert_prefixed(run_new(home.path(), &dest).unwrap_err(), "new");
}
#[test]
fn new_at_an_existing_plain_file_names_the_rule_not_the_errno() {
let home = TempDir::new().unwrap();
let tmp = TempDir::new().unwrap();
let dest = tmp.path().join("afile");
std::fs::write(&dest, b"x").unwrap();
let err = run_new(home.path(), &dest).unwrap_err();
assert_eq!(
err.to_string(),
format!(
"lernie new: destination {} already exists and is not a directory",
dest.display()
)
);
assert_eq!(std::fs::read(&dest).unwrap(), b"x");
}
#[test]
fn config_authors_a_commit() {
let (_h, ws) = fixture::workspace();
let args = config::Args {
workspace: ws,
name: None,
from: None,
orphan: false,
};
let home = TempDir::new().unwrap();
let (r, ..) = with_lernie_home(home.path(), || {
with_fx("lernie", b"", &writing_editor, |fx| config::run(args, fx))
});
assert!(matches!(r.unwrap(), Outcome::Quiet));
}
#[test]
fn config_reports_a_declined_pass_as_a_clean_line() {
let (_h, ws) = fixture::workspace();
let args = || config::Args {
workspace: ws.clone(),
name: None,
from: None,
orphan: false,
};
let home = TempDir::new().unwrap();
let line = with_lernie_home(home.path(), || {
let (first, ..) = with_fx("lernie", b"", &noop_editor, |fx| config::run(args(), fx));
first.unwrap();
let (r, ..) = with_fx("lernie", b"", &noop_editor, |fx| config::run(args(), fx));
let Outcome::Line(line) = r.unwrap() else {
panic!("a declined pass reports the branch that did not move")
};
line
});
assert!(line.starts_with("config/default unchanged: "), "{line}");
assert!(!ws.join(".config-author").exists());
}
#[test]
fn config_reports_a_non_workspace() {
let tmp = TempDir::new().unwrap();
let (r, ..) = with_fx("lernie", b"", &noop_editor, |fx| {
config::run(
config::Args {
workspace: tmp.path().to_path_buf(),
name: None,
from: None,
orphan: false,
},
fx,
)
});
assert_prefixed(r.unwrap_err(), "config");
}
#[test]
fn prompt_reports_a_non_workspace() {
let tmp = TempDir::new().unwrap();
let (r, ..) = with_fx("lernie", b"", &noop_editor, |fx| {
prompt::run(
prompt::Args {
repo: tmp.path().to_path_buf(),
message: "hi".into(),
from: None,
config: None,
name: None,
pin: vec![],
},
fx,
)
});
assert_prefixed(r.unwrap_err(), "prompt");
}
#[test]
fn dispatch_forks_a_child_through_the_front_door() {
let (_h, ws) = fixture::workspace();
fixture::spawn_root(&ws, "20260101-p1");
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
dispatch::run(
dispatch::Args {
role: "worker".into(),
repo: ws.clone(),
branch: "20260101-p1".into(),
goal: Some("do the thing".into()),
from: None,
name: None,
pin: vec![],
},
fx,
)
});
assert!(matches!(r.unwrap(), Outcome::Quiet));
}
#[test]
fn dispatch_reports_an_undefined_role_with_its_prefix() {
let (_h, ws) = fixture::workspace();
fixture::spawn_root(&ws, "p1");
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
dispatch::run(
dispatch::Args {
role: "no-such".into(),
repo: ws.clone(),
branch: "p1".into(),
goal: Some("g".into()),
from: None,
name: None,
pin: vec![],
},
fx,
)
});
assert_prefixed(r.unwrap_err(), "dispatch no-such");
}