mod common;
use std::collections::HashMap;
use std::sync::Arc;
use common::{event_kinds, fixed_clock, fixed_random, fixed_run_id};
use salvor_engine::{EngineError, run_graph};
use salvor_graph::{FoldBody, FoldJoin, FoldSpec, Graph, GraphBuilder, ToolSpec};
use salvor_runtime::RunCtx;
use salvor_store::{EventStore, SqliteStore};
use salvor_tools::DynTool;
use serde_json::json;
fn lone_fold_graph() -> Graph {
let body = GraphBuilder::new()
.tool(ToolSpec::new("inner", "inner_tool"))
.build();
GraphBuilder::new()
.fold(FoldSpec::new(
"refine",
FoldBody::Subgraph(Box::new(body)),
3,
"score >= 0.85",
FoldJoin::BestBy("score".into()),
))
.build()
}
#[tokio::test]
async fn a_fold_node_is_refused_before_it_records_anything() {
let agents: HashMap<String, salvor_runtime::Agent> = HashMap::new();
let tools: HashMap<String, Box<dyn DynTool>> = HashMap::new();
let graph = lone_fold_graph();
let run_id = fixed_run_id(40);
let store = Arc::new(SqliteStore::in_memory().expect("store opens"));
let mut ctx = RunCtx::with_hooks(store.clone(), run_id, vec![], fixed_clock(), fixed_random())
.expect("ctx builds");
let error = run_graph(&mut ctx, &graph, &json!({"score": 0.9}), &agents, &tools)
.await
.expect_err("a fold node must be refused while its execution is deferred");
match error {
EngineError::UnsupportedNode { node, kind } => {
assert_eq!(node, "refine");
assert_eq!(kind, "fold");
}
other => panic!("expected UnsupportedNode, got {other:?}"),
}
let log = store.read_log(run_id).await.expect("log reads");
assert_eq!(
event_kinds(&log),
["GraphRunStarted"],
"the refusal leaves only the head; no NodeEntered for the fold, no terminal"
);
assert!(
!log.iter().any(|e| matches!(
&e.event,
salvor_core::Event::NodeEntered { node } if node == "refine"
)),
"the fold must not have been entered"
);
}