use super::super::{Claim, DELETE_STEP, DeleteError, Step, confirmation, execute, plan};
use crate::cli_outbound::Cli;
use crate::opslog::{self, OpEntry, SYNTHETIC_EXIT, YOG_STEP};
use crate::ui_state::UiState;
use std::fs;
use std::path::PathBuf;
use tempfile::{TempDir, tempdir};
use super::super::tests::{NAME, claim};
struct World {
bin: TempDir,
state: TempDir,
project: TempDir,
names: TempDir,
}
impl World {
fn new() -> Self {
let me = Self {
bin: tempdir().unwrap(),
state: tempdir().unwrap(),
project: tempdir().unwrap(),
names: tempdir().unwrap(),
};
fs::create_dir_all(me.workspace().join("repo.git")).unwrap();
me
}
fn workspace(&self) -> PathBuf {
self.names.path().join(NAME)
}
fn ui(&self) -> UiState {
UiState::open(self.state.path().join("ui.json"))
}
fn ops(&self) -> Vec<OpEntry> {
opslog::tail(self.state.path(), 16)
}
fn bl(&self, code: i32) -> Cli {
let path = self.bin.path().join("bl");
crate::test_support::write_exec(
&path,
&format!("#!/bin/sh\necho \"$@\" 1>&2\nexit {code}\n"),
);
Cli::new(path)
}
fn wall_root(&self) -> std::path::PathBuf {
self.names.path().join("world")
}
fn seed_wall(&self) -> PathBuf {
let wall = crate::world::wall::root_under(&self.wall_root(), NAME);
let creds = crate::config_edit::brazen::BrazenPaths::in_wall(&wall).credentials_dir;
fs::create_dir_all(&creds).unwrap();
fs::write(creds.join("openai.json"), b"{}").unwrap();
wall
}
fn steps(&self, claims: Vec<Claim>) -> Vec<Step> {
plan(
&confirmation(NAME, &[], claims),
&self.workspace(),
&self.wall_root(),
&[self.project.path().to_path_buf()],
)
}
}
#[test]
fn execute_releases_prunes_and_removes_leaving_one_step_row() {
let w = World::new();
let key = w.workspace().to_string_lossy().into_owned();
let mut ui = w.ui();
ui.set_pinned(vec![key.clone()]);
let claims = vec![claim(&crate::naming::leaf(w.project.path()), "bl-7")];
execute(&w.steps(claims), &w.bl(0), &mut ui, w.state.path(), "TS").unwrap();
assert!(!w.workspace().exists(), "the sphere wall is down");
assert!(ui.pinned().is_empty(), "the dead workspace's pin is pruned");
let ops = w.ops();
assert_eq!(&ops[0].argv[1..], &["unclaim", "bl-7", "--as", NAME]);
assert_eq!(ops[0].cwd, w.project.path().display().to_string());
assert_eq!(&ops[1].argv, &[YOG_STEP.to_owned(), DELETE_STEP.to_owned()]);
assert_eq!(ops[1].exit, 0, "a completed step states its status");
assert_eq!(ops[1].cwd, w.names.path().display().to_string());
}
#[test]
fn the_sphere_takes_its_wall_down_with_it() {
let w = World::new();
let wall = w.seed_wall();
let mut ui = w.ui();
execute(
&w.steps(Vec::new()),
&w.bl(0),
&mut ui,
w.state.path(),
"TS",
)
.unwrap();
assert!(!wall.exists(), "the wall went with the workspace");
assert!(!w.workspace().exists());
}
#[test]
fn a_wall_that_cannot_be_removed_aborts_the_unmaking() {
let w = World::new();
let wall = crate::world::wall::root_under(&w.wall_root(), NAME);
fs::create_dir_all(wall.parent().expect("the walls dir")).unwrap();
fs::write(&wall, b"not a dir").unwrap();
let mut ui = w.ui();
let err = execute(
&w.steps(Vec::new()),
&w.bl(0),
&mut ui,
w.state.path(),
"TS",
)
.unwrap_err();
assert!(matches!(err, DeleteError::Io(_)));
assert!(w.workspace().exists(), "nothing was half-unmade");
assert_eq!(w.ops()[0].exit, SYNTHETIC_EXIT);
}
#[test]
fn a_refused_release_aborts_before_the_removal() {
let w = World::new();
let mut ui = w.ui();
let claims = vec![claim(&crate::naming::leaf(w.project.path()), "bl-7")];
let err = execute(&w.steps(claims), &w.bl(3), &mut ui, w.state.path(), "TS").unwrap_err();
assert!(matches!(err, DeleteError::ReleaseFailed { ref id, .. } if id == "bl-7"));
assert!(
err.to_string()
.starts_with("`bl unclaim bl-7` failed (exit 3)")
);
assert!(
w.workspace().exists(),
"releases first, removal last — the wall stands"
);
assert_eq!(w.ops().len(), 1, "only the refused unclaim's own row");
}
#[test]
fn a_failed_removal_leaves_a_synthetic_step_row() {
let w = World::new();
let mut ui = w.ui();
let steps = plan(
&confirmation(NAME, &[], Vec::new()),
&w.names.path().join("never-minted"),
&w.wall_root(),
&[],
);
let err = execute(&steps, &w.bl(0), &mut ui, w.state.path(), "TS").unwrap_err();
assert!(matches!(err, DeleteError::Io(_)));
let ops = w.ops();
assert_eq!(&ops[0].argv, &[YOG_STEP.to_owned(), DELETE_STEP.to_owned()]);
assert_eq!(ops[0].exit, SYNTHETIC_EXIT);
assert!(!ops[0].stderr.is_empty(), "the failure text is the record");
}
#[test]
fn a_spawn_failure_rides_back_as_io() {
let w = World::new();
let mut ui = w.ui();
let claims = vec![claim(&crate::naming::leaf(w.project.path()), "bl-7")];
let bl = Cli::new(w.bin.path().join("no-such-bl"));
let err = execute(&w.steps(claims), &bl, &mut ui, w.state.path(), "TS").unwrap_err();
assert!(matches!(err, DeleteError::Io(_)));
assert!(w.workspace().exists());
assert_eq!(
w.ops()[0].exit,
SYNTHETIC_EXIT,
"the un-launched spawn's row"
);
}
#[test]
fn the_refusal_errors_say_what_they_refused() {
assert_eq!(
DeleteError::NotArmed.to_string(),
"type the workspace's name to confirm"
);
assert_eq!(
DeleteError::Unnamed.to_string(),
"not a yog-named workspace"
);
}