use crate::binding::{work_worktree_path, workspace_path};
use crate::projects::join::JoinState;
use crate::start::{
BallSpec, Payload, StartInputs, Step, is_resume_eligible, is_start_eligible, plan,
};
use std::path::{Path, PathBuf};
const NAME: &str = "cobalt-gecko";
const YOG: &str = "/yog";
const BALLS: &str = "/balls";
const HOME: &str = "/home/op";
const PROJ: &str = "/proj";
fn inputs(payload: Payload) -> StartInputs {
StartInputs {
workspace: ws(),
repo: payload.project().map(|_| PathBuf::from(PROJ)),
payload,
home: PathBuf::from(HOME),
yog_data_root: PathBuf::from(YOG),
balls_state_root: PathBuf::from(BALLS),
conversation_names: Vec::new(),
}
}
fn ws() -> PathBuf {
workspace_path(Path::new(YOG), NAME)
}
fn ball_payload(join: JoinState) -> Payload {
Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::Existing {
id: "bl-1".to_owned(),
title: "T".to_owned(),
body: "B".to_owned(),
join,
tags: Vec::new(),
},
}
}
#[test]
fn bare_plans_seed_new_prompt() {
assert_eq!(
plan(&inputs(Payload::Bare)),
vec![
Step::EnsureSeeded,
Step::EnsureWorkspace { workspace: ws() },
Step::Prompt {
workspace: NAME.to_owned(),
binding: None,
goal: String::new(),
},
]
);
}
#[test]
fn path_rung_targets_the_dir_verbatim() {
let dir = PathBuf::from("/work/here");
let steps = plan(&inputs(Payload::Path { dir: dir.clone() }));
assert!(matches!(steps[0], Step::EnsureSeeded));
assert!(matches!(steps[1], Step::EnsureWorkspace { .. }));
let Some(Step::Prompt {
binding,
goal,
workspace,
}) = steps.last()
else {
panic!("path plan ends in a prompt");
};
assert_eq!(binding.as_ref(), Some(&dir), "the binding is the directory");
assert_eq!(workspace, NAME);
assert!(
goal.starts_with("Working directory: /work/here"),
"the target preamble leads with the dir (§3.3 headline-first)"
);
assert!(
!goal.contains("You are"),
"the identity line is stamped at fire, not here"
);
}
#[test]
fn ball_ready_claims_after_new_and_binds_the_worktree() {
let steps = plan(&inputs(ball_payload(JoinState::ReadyStartable)));
assert!(matches!(steps[0], Step::EnsureSeeded));
assert!(matches!(steps[1], Step::EnsureWorkspace { .. }));
assert_eq!(
steps[2],
Step::Claim {
project: PathBuf::from(PROJ),
id: "bl-1".to_owned(),
name: NAME.to_owned(),
},
"claim stamped with the workspace name, after the substrate"
);
let wt = work_worktree_path(Path::new(BALLS), Path::new(PROJ), "bl-1", None);
let Step::Prompt { binding, goal, .. } = &steps[3] else {
panic!("ends in a prompt");
};
assert_eq!(
binding.as_ref(),
Some(&wt),
"the binding is the work worktree"
);
assert_eq!(goal, "Ball bl-1: T\n\nB");
assert!(
!goal.contains(&wt.display().to_string()),
"and the path is a parameter, never goal prose (bl-6654)"
);
}
#[test]
fn ball_bound_drops_the_claim() {
let steps = plan(&inputs(ball_payload(JoinState::Bound)));
assert!(!steps.iter().any(|s| matches!(s, Step::Claim { .. })));
assert!(steps.iter().any(|s| matches!(s, Step::Prompt { .. })));
assert_eq!(steps.len(), 3, "seed, new, prompt");
}
#[test]
fn ball_new_defers_to_a_single_create_after_the_substrate() {
let payload = Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::New {
title: "Fresh".to_owned(),
body: "do".to_owned(),
},
};
assert_eq!(
plan(&inputs(payload)),
vec![
Step::EnsureSeeded,
Step::EnsureWorkspace { workspace: ws() },
Step::Create {
project: PathBuf::from(PROJ),
title: "Fresh".to_owned(),
body: "do".to_owned(),
},
],
"create is deferred to after seed+new; the id re-plans the rest"
);
}
#[test]
fn eligibility_is_the_ready_ball() {
assert!(is_start_eligible(JoinState::ReadyStartable));
assert!(!is_start_eligible(JoinState::Bound));
assert!(!is_start_eligible(JoinState::Blocked));
assert!(!is_start_eligible(JoinState::ClaimedElsewhere));
assert!(!is_start_eligible(JoinState::Delivered));
assert!(!is_start_eligible(JoinState::UnassignedWorkspace));
assert!(!is_start_eligible(JoinState::OrphanedProject));
}
#[test]
fn resume_eligibility_is_the_bound_ball() {
assert!(is_resume_eligible(JoinState::Bound));
assert!(!is_resume_eligible(JoinState::ReadyStartable));
assert!(!is_resume_eligible(JoinState::Blocked));
assert!(!is_resume_eligible(JoinState::ClaimedElsewhere));
assert!(!is_resume_eligible(JoinState::Delivered));
assert!(!is_resume_eligible(JoinState::UnassignedWorkspace));
assert!(!is_resume_eligible(JoinState::OrphanedProject));
}
#[test]
fn the_rung_decides_which_surface_the_starts_failures_banner_on() {
use crate::opslog::Origin;
assert_eq!(Payload::Bare.origin(), Origin::Conversation);
assert_eq!(
Payload::Path {
dir: PathBuf::from("/work")
}
.origin(),
Origin::Conversation
);
let existing = Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::Existing {
id: "bl-7".to_owned(),
title: "T".to_owned(),
body: "B".to_owned(),
join: JoinState::ReadyStartable,
tags: Vec::new(),
},
};
assert_eq!(existing.origin(), Origin::Balls);
let new = Payload::Ball {
project: "proj".to_owned(),
ball: BallSpec::New {
title: "T".to_owned(),
body: "B".to_owned(),
},
};
assert_eq!(new.origin(), Origin::Balls);
}