use super::*;
use crate::workspace::fixture;
use tempfile::TempDir;
fn scaffolded_repo_with_parent(parent: &str) -> (TempDir, std::path::PathBuf) {
let (holder, repo) = fixture::workspace();
fixture::spawn_root(&repo, parent);
(holder, repo)
}
struct NoopLauncher;
impl Launcher for NoopLauncher {
fn launch(&self, _workspace: &Path, _agent_id: &str) -> std::io::Result<()> {
Ok(())
}
}
fn sub_count(repo: &Path, parent: &str) -> usize {
std::fs::read_dir(repo.join(crate::workspace::AGENTS_DIR))
.unwrap()
.flatten()
.filter(|e| {
e.file_name()
.to_string_lossy()
.starts_with(&format!("{parent}-"))
})
.count()
}
#[test]
fn compactor_dispatch_forks_an_ordinary_compactor_child() {
let (_holder, repo) = scaffolded_repo_with_parent("20260101-p1");
run_with(ROLE_COMPACTOR, &repo, "20260101-p1", None, &NoopLauncher).unwrap();
assert_eq!(sub_count(&repo, "20260101-p1"), 1);
}
#[test]
fn worker_dispatch_succeeds_and_spawns_a_sub_branch() {
let (_holder, repo) = scaffolded_repo_with_parent("20260101-p1");
run_with(
"worker",
&repo,
"20260101-p1",
Some("do the thing"),
&NoopLauncher,
)
.unwrap();
assert_eq!(sub_count(&repo, "20260101-p1"), 1);
}
#[test]
fn any_config_role_dispatches_open_set() {
let (_holder, repo) = fixture::workspace();
let yaml = "roles:\n worker:\n provider: anthropic\n model: sonnet\n \
verifier:\n provider: anthropic\n model: sonnet\n";
fixture::amend_config(
&repo,
&[("providers.yaml", yaml), ("souls/verifier.md", "v\n")],
);
fixture::spawn_root(&repo, "p9");
run_with("verifier", &repo, "p9", Some("judge it"), &NoopLauncher).unwrap();
assert_eq!(sub_count(&repo, "p9"), 1);
}
#[test]
fn a_path_that_is_not_a_workspace_is_the_shared_layout_decline() {
let holder = TempDir::new().unwrap();
let err = run_with(
"worker",
holder.path(),
"someagent",
Some("hi"),
&NoopLauncher,
)
.unwrap_err();
assert!(matches!(err, DispatchCliError::Layout(_)), "{err}");
assert_eq!(
err.to_string(),
format!(
"{} is not a workspace (no repo.git) — create one with `lernie new` (ARCH §2.2)",
holder.path().display()
)
);
}
#[test]
fn a_parent_with_no_agent_ref_is_the_shared_existence_decline() {
let (_holder, repo) = scaffolded_repo_with_parent("p1");
let err = run_with("worker", &repo, "nosuchparent", Some("hi"), &NoopLauncher).unwrap_err();
assert!(matches!(err, DispatchCliError::UnknownParent(_)), "{err}");
assert_eq!(
err.to_string(),
"no agent \"nosuchparent\" in this workspace — a child forks off an existing parent \
(ARCH §2.5); check the id against the workspace's `agents/*` refs, or start an \
agent with `lernie prompt` / `lernie dispatch`"
);
}
#[test]
fn undefined_role_names_the_roles_that_are_defined() {
let (_holder, repo) = scaffolded_repo_with_parent("p1");
let err = run_with("no-such-role", &repo, "p1", Some("g"), &NoopLauncher).unwrap_err();
assert!(matches!(err, DispatchCliError::InvalidRole(_)), "{err}");
assert_eq!(
err.to_string(),
"role \"no-such-role\" is not defined in the providers.yaml governing agent \"p1\" \
— defined roles: compactor, worker"
);
}
#[test]
fn worker_requires_a_goal() {
let (_holder, repo) = scaffolded_repo_with_parent("p1");
let err = run("worker", &repo, "p1", None, Path::new("true")).unwrap_err();
assert_eq!(err.to_string(), "--goal is required for role \"worker\"");
}
#[test]
fn compactor_rejects_a_goal() {
let (_holder, repo) = scaffolded_repo_with_parent("p1");
let err = run(ROLE_COMPACTOR, &repo, "p1", Some("g"), Path::new("true")).unwrap_err();
assert_eq!(
err.to_string(),
"--goal is not accepted for role \"compactor\""
);
}
#[test]
fn inner_errors_render_through_the_shared_display() {
let (_holder, repo) = scaffolded_repo_with_parent("p1");
std::fs::remove_dir_all(repo.join(crate::workspace::AGENTS_DIR).join("p1")).unwrap();
let err = run_with("worker", &repo, "p1", Some("g"), &NoopLauncher).unwrap_err();
assert!(matches!(err, DispatchCliError::Inner(_)), "{err}");
assert!(!err.to_string().is_empty());
}