use super::{World, ball, fake_fail, fake_litany};
use crate::binding::{work_worktree_path, workspace_path};
use crate::cli_outbound::Cli;
use crate::opslog::{Origin, SYNTHETIC_EXIT, YOG_STEP};
use crate::projects::join::JoinState;
use crate::start::{
Deps, Payload, StartError, execute_ensure_workspace, on_mint, resolve_worktree,
};
use crate::world::{Layout, layout_under};
use litany::mint::MintError;
use std::path::PathBuf;
fn layout(w: &World) -> Layout {
layout_under(w.yog.path())
}
fn deps(w: &World, litany: &Cli) -> Deps {
Deps {
bl: Cli::new("/no/bl"),
litany: litany.clone(),
state_root: w.state.path().to_path_buf(),
yog_binary: PathBuf::from("/no/yog"),
}
}
#[test]
fn ensure_skips_when_the_workspace_already_exists() {
let w = World::new();
let ws = workspace_path(w.yog.path(), "n");
std::fs::create_dir_all(ws.join("repo.git")).unwrap();
let litany = Cli::new("/definitely/not/a/real/litany");
let created = execute_ensure_workspace(
&deps(&w, &litany),
"TS",
&ws,
"default",
&layout(&w),
Origin::Balls,
)
.unwrap();
assert!(!created, "existing workspace skipped");
assert!(w.ops().is_empty(), "skip runs and logs nothing");
}
#[test]
fn ensure_creates_the_workspace_and_logs() {
let w = World::new();
let ws = workspace_path(w.yog.path(), "cobalt-gecko");
let litany = Cli::new(fake_litany(w.bin.path()));
assert!(
execute_ensure_workspace(
&deps(&w, &litany),
"TS",
&ws,
"default",
&layout(&w),
Origin::Balls
)
.unwrap()
);
assert!(ws.parent().unwrap().is_dir(), "parent chain mkdir -p'd");
assert_eq!(
&w.ops()[0].argv[1..],
&["new", ws.to_string_lossy().as_ref()]
);
}
#[test]
fn ensure_errors_and_logs_on_a_nonzero_new() {
let w = World::new();
let ws = workspace_path(w.yog.path(), "n");
let litany = Cli::new(fake_fail(w.bin.path(), "litany", "disk full"));
let err = execute_ensure_workspace(
&deps(&w, &litany),
"TS",
&ws,
"default",
&layout(&w),
Origin::Balls,
)
.unwrap_err();
assert!(matches!(err, StartError::VerbFailed { verb: "new", .. }));
}
#[test]
fn ensure_logs_a_mkdir_step_failure() {
let w = World::new();
let blocker = w.yog.path().join("blocked");
std::fs::write(&blocker, b"x").unwrap();
let ws = blocker.join("workspaces").join("n");
let litany = Cli::new("/definitely/not/a/real/litany");
let err = execute_ensure_workspace(
&deps(&w, &litany),
"TS",
&ws,
"default",
&layout(&w),
Origin::Balls,
)
.unwrap_err();
assert!(matches!(err, StartError::Io(_)));
assert_eq!(w.ops()[0].argv, [YOG_STEP, "mkdir"]);
}
#[test]
fn ensure_creates_whatever_the_birth_template_names() {
let w = World::new();
let tmpl = layout(&w).litany.join("template");
std::fs::create_dir_all(&tmpl).unwrap();
std::fs::write(
tmpl.join("providers.yaml"),
"roles:\n worker:\n provider: codex\n model: gpt-5.4\n",
)
.unwrap();
let ws = workspace_path(w.yog.path(), "n");
let litany = Cli::new(fake_litany(w.bin.path()));
assert!(
execute_ensure_workspace(
&deps(&w, &litany),
"TS",
&ws,
"default",
&layout(&w),
Origin::Balls
)
.unwrap()
);
assert!(
!w.ops().iter().any(|e| e.argv == [YOG_STEP, "template"]),
"no birth-time provider step remains"
);
}
#[test]
fn on_mint_passes_a_name_through() {
let w = World::new();
let name = on_mint(
Ok("cobalt-gecko".to_owned()),
w.state.path(),
"TS",
w.home.path(),
Origin::Conversation,
)
.unwrap();
assert_eq!(name, "cobalt-gecko");
assert!(w.ops().is_empty(), "a clean mint logs nothing");
}
#[test]
fn on_mint_logs_an_exhausted_pool() {
let w = World::new();
let err = on_mint(
Err(MintError::Exhausted(6)),
w.state.path(),
"TS",
w.home.path(),
Origin::Conversation,
)
.unwrap_err();
assert!(matches!(err, StartError::Mint(MintError::Exhausted(6))));
let e = &w.ops()[0];
assert_eq!(e.argv, [YOG_STEP, "mint"]);
assert_eq!(e.exit, SYNTHETIC_EXIT);
assert!(e.stderr.contains("pool exhausted"));
}
#[test]
fn resolve_worktree_prefers_the_claim_then_disk_then_canonical() {
let w = World::new();
let (balls, project, name) = (w.balls.path(), w.project.path(), "cobalt-gecko");
let existing = |id: &str| ball(project, id, JoinState::Bound);
assert_eq!(
resolve_worktree(&Payload::Bare, Some(project), balls, name, None),
None
);
let claimed = PathBuf::from("/claimed/wt-suffixed");
assert_eq!(
resolve_worktree(
&existing("bl-1"),
Some(project),
balls,
name,
Some(claimed.clone())
),
Some(claimed),
);
assert_eq!(
resolve_worktree(&existing("bl-2"), Some(project), balls, name, None),
Some(work_worktree_path(balls, project, "bl-2", None)),
);
let suffixed = work_worktree_path(balls, project, "bl-3", Some(name));
std::fs::create_dir_all(&suffixed).unwrap();
assert_eq!(
resolve_worktree(&existing("bl-3"), Some(project), balls, name, None),
Some(suffixed),
);
}