use ri_agent_graph::prelude::*;
#[tokio::test]
async fn test_ordinary_error_is_failed_not_complete() {
let graph = AgentGraph::builder()
.add_node(
"fail_node",
node!(|_state| async move {
Err::<(), AgentGraphError>(AgentGraphError::ExecutionError(
"intentional failure".to_string(),
))
}),
)
.build()
.unwrap();
let state = AgentState::new();
let result = graph
.execute_with_interrupt("fail_node", state, GraphConfig::default())
.await;
match result {
ExecutionResult::Failed { error, .. } => {
let msg = error.to_string();
assert!(
msg.contains("intentional failure"),
"error message should contain the original failure, got: {msg}"
);
}
ExecutionResult::Complete(_) => {
panic!("AG-001: ordinary error was silently mapped to Complete (false success)");
}
ExecutionResult::Interrupted { .. } => {
panic!("AG-001: ordinary error was misclassified as Interrupted");
}
}
}
#[tokio::test]
async fn test_cancellation_is_failed_not_complete() {
let graph = AgentGraph::builder()
.add_node(
"step1",
node!(|state| async move {
state.set("step1_done", true).await?;
Ok(())
}),
)
.add_node(
"step2",
node!(|state| async move {
state.set("step2_done", true).await?;
Ok(())
}),
)
.add_edge("step1", "step2")
.build()
.unwrap();
let config = GraphConfig {
recursion_limit: 0,
..Default::default()
};
let result = graph
.execute_with_interrupt("step1", AgentState::new(), config)
.await;
match result {
ExecutionResult::Failed { error, .. } => {
assert!(
error.to_string().contains("iterations") || error.to_string().contains("cycle"),
"expected max iterations or cycle error, got: {error}"
);
}
ExecutionResult::Complete(_) => {
panic!("AG-001: max-iterations error was silently mapped to Complete");
}
ExecutionResult::Interrupted { .. } => {
panic!("AG-001: max-iterations error was misclassified as Interrupted");
}
}
}
#[tokio::test]
async fn test_successful_execution_still_completes() {
let graph = AgentGraph::builder()
.add_node(
"step1",
node!(|state| async move {
state.set("done", true).await?;
Ok(())
}),
)
.build()
.unwrap();
let result = graph
.execute_with_interrupt("step1", AgentState::new(), GraphConfig::default())
.await;
match result {
ExecutionResult::Complete(state) => {
assert!(state.get::<bool>("done").await.unwrap());
}
ExecutionResult::Failed { error, .. } => {
panic!("successful execution should return Complete, not Failed: {error}");
}
ExecutionResult::Interrupted { .. } => {
panic!("successful execution should return Complete, not Interrupted");
}
}
}
#[tokio::test]
async fn test_interrupt_still_returns_interrupted() {
let graph = AgentGraph::builder()
.add_node(
"step1",
node!(|state| async move {
state.set("step1_done", true).await?;
Ok(())
}),
)
.add_node(
"step2",
node!(|state| async move {
state.set("step2_done", true).await?;
Ok(())
}),
)
.add_edge("step1", "step2")
.with_interrupt_before(vec!["step2".to_string()])
.build()
.unwrap();
let result = graph
.execute_with_interrupt("step1", AgentState::new(), GraphConfig::default())
.await;
match result {
ExecutionResult::Interrupted { node, .. } => {
assert_eq!(node, "step2");
}
ExecutionResult::Complete(_) => {
panic!("should have been interrupted, not completed");
}
ExecutionResult::Failed { error, .. } => {
panic!("interrupt should return Interrupted, not Failed: {error}");
}
}
}