use super::*;
use newt_core::PlanDraftSink as _;
use std::sync::Mutex;
fn objective(text: &str) -> newt_core::TurnPromptContext {
newt_core::TurnPromptContext::ephemeral_operator("conv-1", text, text)
}
fn root(ctx: &newt_core::TurnPromptContext) -> newt_core::PromptId {
ctx.active().root_prompt_id()
}
fn recording_persist(
log: &Mutex<Vec<(std::path::PathBuf, String)>>,
) -> impl Fn(&std::path::Path, &str) -> std::io::Result<()> + Sync + '_ {
move |path, markdown| {
log.lock()
.unwrap()
.push((path.to_path_buf(), markdown.to_string()));
Ok(())
}
}
fn failing_persist(_: &std::path::Path, _: &str) -> std::io::Result<()> {
Err(std::io::Error::other("disk full"))
}
#[test]
fn revision_counts_per_objective() {
let a = root(&objective("A"));
let b = root(&objective("B"));
assert_eq!(next_plan_draft_revision(None, a), 1);
let held = ObjectiveDraft {
objective: a,
draft: newt_core::agentic::PlanDraft {
revision: 3,
markdown: String::new(),
},
};
assert_eq!(next_plan_draft_revision(Some(&held), a), 4);
assert_eq!(
next_plan_draft_revision(Some(&held), b),
1,
"a new objective starts its own slot"
);
}
#[test]
fn save_persists_through_the_seam_and_is_scoped_to_its_objective() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let state = PlanDraftState::default();
let a = objective("A");
let sink_a = state.bind("conv-1", Some(root(&a)), &persist);
assert!(
sink_a.latest_draft().is_none(),
"a fresh state has no draft"
);
assert_eq!(sink_a.save_draft("# v1".into()), Ok(1));
assert_eq!(sink_a.save_draft("# v2".into()), Ok(2));
let writes = log.lock().unwrap().clone();
assert_eq!(writes.len(), 2);
assert_eq!(writes[1].0, newt_core::session_plan_path("conv-1"));
assert_eq!(writes[1].1, "# v2");
assert_eq!(
sink_a.latest_draft().map(|d| d.markdown),
Some("# v2".to_string())
);
let b = objective("B");
let sink_b = state.bind("conv-1", Some(root(&b)), &persist);
assert!(
sink_b.latest_draft().is_none(),
"objective B must not see A's draft"
);
state.clear();
assert!(sink_a.latest_draft().is_none());
}
#[test]
fn a_persist_failure_is_an_error_and_leaves_the_slot_untouched() {
let state = PlanDraftState::default();
let a = objective("A");
let sink = state.bind("conv-1", Some(root(&a)), &failing_persist);
let err = sink.save_draft("# v1".into()).unwrap_err();
assert!(err.contains("disk full"), "{err}");
assert!(sink.latest_draft().is_none());
assert!(state.take_for_presentation(root(&a)).is_none());
}
#[test]
fn no_objective_refuses_to_save() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let state = PlanDraftState::default();
let sink = state.bind("conv-1", None, &persist);
assert!(sink.save_draft("# v1".into()).is_err());
assert!(log.lock().unwrap().is_empty(), "nothing was written");
}
#[test]
fn take_for_presentation_shows_each_revision_exactly_once() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let state = PlanDraftState::default();
let a = objective("A");
let sink = state.bind("conv-1", Some(root(&a)), &persist);
assert!(state.take_for_presentation(root(&a)).is_none());
sink.save_draft("# v1".into()).unwrap();
let shown = state
.take_for_presentation(root(&a))
.expect("a fresh draft to show");
assert_eq!(shown.draft.revision, 1);
assert_eq!(
shown,
newt_core::agentic::PresentedPlan::new(shown.draft.clone()),
"the snapshot id is content-addressed over the shown bytes"
);
assert!(
state.take_for_presentation(root(&a)).is_none(),
"the same revision must never be presented twice"
);
sink.save_draft("# v2".into()).unwrap();
let again = state
.take_for_presentation(root(&a))
.expect("a genuinely new revision is offered again");
assert_eq!(again.draft.revision, 2);
}
#[test]
fn a_draft_presented_under_a_is_neither_shown_nor_approved_under_b() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let state = PlanDraftState::default();
let a = objective("A");
state
.bind("conv-1", Some(root(&a)), &persist)
.save_draft("# plan A".into())
.unwrap();
assert!(state.take_for_presentation(root(&a)).is_some());
let b = objective("B");
assert!(state.take_for_presentation(root(&b)).is_none());
assert!(
state.take_approved(root(&b)).is_none(),
"approval under B must not seed A's plan"
);
assert_eq!(
state.take_approved(root(&a)).map(|p| p.draft.markdown),
Some("# plan A".to_string()),
"A's own approval still sees A's snapshot"
);
}
#[test]
fn a_continuation_keeps_the_snapshot_and_approval_consumes_it_once() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let state = PlanDraftState::default();
let a = objective("A");
state
.bind("conv-1", Some(root(&a)), &persist)
.save_draft("# plan A".into())
.unwrap();
let shown = state.take_for_presentation(root(&a)).unwrap();
let discuss =
newt_core::TurnPromptContext::ephemeral_operator_continuation("conv-1", "why?", "why?", &a)
.unwrap();
assert_eq!(root(&discuss), root(&a), "continuation keeps the lineage");
assert!(
state.take_for_presentation(root(&discuss)).is_none(),
"no new revision: nothing re-presented"
);
let approved = state
.take_approved(root(&discuss))
.expect("the shown snapshot");
assert_eq!(
approved, shown,
"exactly the presented snapshot is approved"
);
assert!(
state.take_approved(root(&discuss)).is_none(),
"a second approval cannot consume it again"
);
}
#[test]
fn conversation_mode_states_clear_drops_the_plan_draft_and_snapshot_too() {
let log = Mutex::new(Vec::new());
let persist = recording_persist(&log);
let states = ConversationModeStates::default();
let a = objective("A");
states
.plan_draft
.bind("conv-1", Some(root(&a)), &persist)
.save_draft("# Draft".into())
.unwrap();
assert!(states.plan_draft.take_for_presentation(root(&a)).is_some());
states.clear();
assert!(states
.plan_draft
.bind("conv-1", Some(root(&a)), &persist)
.latest_draft()
.is_none());
assert!(states.plan_draft.take_approved(root(&a)).is_none());
}
#[test]
#[ignore = "real-fs tier; weekly/release lanes run with --ignored --test-threads=1"]
#[serial_test::serial(real_fs)]
fn persist_plan_to_disk_writes_the_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("sessions").join("s1").join("plan.md");
persist_plan_to_disk(&path, "# real").unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "# real");
persist_plan_to_disk(&path, "# real v2").unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "# real v2");
}