use super::prompt::{make_fifo, prepared, workspace};
use super::{World, fake_bl, write_exec};
use crate::binding::workspace_path;
use crate::cli_outbound::Cli;
use crate::projects::join::JoinState;
use crate::start::{BallSpec, Deps, Payload, execute_prompt, prepare};
use crate::test_support::workspace::{seed_workspace_config, seed_workspace_lineage};
use std::path::Path;
const MANIFEST: &str = "roles:\n worker:\n pinned:\n - goal.md\n";
fn deps(w: &World, bl: &Cli, litany: &Cli) -> Deps {
Deps {
bl: bl.clone(),
litany: litany.clone(),
state_root: w.state.path().to_path_buf(),
yog_binary: std::path::PathBuf::from("/no/yog"),
}
}
fn tagged(project: &Path, tags: &[&str]) -> Payload {
Payload::Ball {
project: crate::naming::leaf(project),
ball: BallSpec::Existing {
id: "bl-1111".to_owned(),
title: "T".to_owned(),
body: "B".to_owned(),
join: JoinState::Bound,
tags: tags.iter().map(|t| (*t).to_owned()).collect(),
},
}
}
fn seeded(w: &World, lineages: &[&str]) -> std::path::PathBuf {
let ws = workspace_path(w.yog.path(), "cobalt-gecko");
seed_workspace_config(
&ws,
&[
("workflow.yaml", "events: {}\n"),
("manifest.yaml", MANIFEST),
],
);
for name in lineages {
seed_workspace_lineage(&ws, name);
}
let litany = crate::world::layout_under(w.yog.path()).litany;
std::fs::create_dir_all(&litany).unwrap();
std::fs::write(litany.join("models.yaml"), b"models: {}\n").unwrap();
ws
}
fn converged(w: &World) -> Vec<String> {
w.ops()
.into_iter()
.filter(|e| e.argv.get(1).map(String::as_str) == Some("config"))
.filter_map(|e| e.argv.get(3).cloned())
.collect()
}
#[test]
fn two_tagged_balls_are_born_on_two_different_lineages() {
for (tags, want) in [
(vec!["deep"], Some("deep")),
(vec!["quick"], Some("quick")),
(vec!["unmapped"], None),
(vec![], None),
] {
let w = World::new();
let ws = seeded(&w, &["deep", "quick"]);
let bl = Cli::new("/no/bl"); let litany = w.litany();
let mut inputs = w.inputs("cobalt-gecko", tagged(w.project.path(), &tags));
inputs.workspace = ws.clone();
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(p.lineage.as_deref(), want, "tags {tags:?}");
assert_eq!(converged(&w), vec![want.unwrap_or("default").to_owned()]);
}
}
#[test]
fn the_fire_carries_the_selected_lineage_and_omits_an_unselected_one() {
for (lineage, want) in [
(Some("deep".to_owned()), vec!["--config", "deep"]),
(None, vec![]),
] {
let w = World::new();
let fifo = w.bin.path().join("report");
make_fifo(&fifo);
let body = format!(
"#!/bin/sh\nprintf '%s\\037%s' \"$4\" \"$5\" > '{}'\n",
fifo.display()
);
let litany = Cli::new(write_exec(w.bin.path(), "litany", &body));
let ws = workspace(&w);
execute_prompt(
&litany,
w.state.path(),
"TS",
&crate::start::Fire {
workspace: ws.clone(),
prepared: crate::start::Prepared {
lineage,
..prepared("cobalt-gecko", None)
},
goal: "do it".to_owned(),
},
&[],
&super::rng(),
)
.unwrap();
let logged = &w.ops()[0].argv;
assert_eq!(logged[4..4 + want.len()], want[..], "lineage {want:?}");
assert_eq!(
logged[logged.len() - 2..],
[ws.to_string_lossy().into_owned(), "do it".to_owned()],
"the workspace and the goal still trail",
);
let recorded = std::fs::read_to_string(&fifo).unwrap();
let seen: Vec<&str> = recorded.split('\u{1f}').collect();
let expect = if want.is_empty() {
vec![ws.to_string_lossy().into_owned(), "do it".to_owned()]
} else {
want.iter().map(|s| (*s).to_owned()).collect()
};
assert_eq!(seen, expect);
}
}
#[test]
fn the_instruction_filename_policy_is_read_off_the_fired_lineage() {
let w = World::new();
let ws = workspace_path(w.yog.path(), "cobalt-gecko");
seed_workspace_config(&ws, &[("instructions.yaml", "- HOUSE.md\n")]);
seed_workspace_lineage(&ws, "deep");
assert_eq!(
crate::start::instructions::names::names(&ws, "deep"),
vec!["HOUSE.md".to_owned()],
"the fork carries the policy it was forked with",
);
assert_eq!(
crate::start::instructions::names::names(&ws, "absent"),
vec!["AGENTS.md".to_owned()],
);
}
#[test]
fn a_freshly_minted_ball_selects_the_default_lineage() {
let w = World::new();
let ws = seeded(&w, &["deep"]);
let wt = crate::binding::work_worktree_path(w.balls.path(), w.project.path(), "bl-2222", None);
let bl = Cli::new(fake_bl(w.bin.path(), "bl-2222", &wt));
let litany = w.litany();
let mut inputs = w.inputs(
"cobalt-gecko",
Payload::Ball {
project: crate::naming::leaf(w.project.path()),
ball: BallSpec::New {
title: "T".to_owned(),
body: "B".to_owned(),
},
},
);
inputs.workspace = ws;
let p = prepare(&deps(&w, &bl, &litany), &inputs, "TS").unwrap();
assert_eq!(p.lineage, None);
}