use crate::{FlowRef, WorkflowGraph, WorkflowNode, WorkflowRef, WorkflowRuntime};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_linear_workflow() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "document-processing", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("extract", "extract-text"))
.add_node(WorkflowNode::new("embed", "embedding").with_input("extract"))
.add_node(WorkflowNode::new("save", "save-results").with_input("embed"))
.add_edge("extract", "embed")
.add_edge("embed", "save");
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let document = FlowRef::new("documents", "doc-1");
let instance_id = runtime
.create_instance(workflow_ref.clone(), "workflow-1", vec![document.clone()])
.expect("Failed to create instance");
runtime
.start_instance(&instance_id)
.expect("Failed to start");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"extract".to_string()));
assert!(!ready.contains(&"embed".to_string()));
runtime
.claim_step(&instance_id, "extract", "peer-a")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "extract", FlowRef::new("texts", "text-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"embed".to_string()));
runtime
.claim_step(&instance_id, "embed", "peer-b")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "embed", FlowRef::new("embeddings", "embed-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"save".to_string()));
runtime
.claim_step(&instance_id, "save", "peer-c")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "save", FlowRef::new("results", "result-1"))
.expect("Failed to complete");
let instance = runtime.get_instance(&instance_id).unwrap();
assert_eq!(instance.state, crate::WorkflowState::Completed);
let ops = runtime.operations_log();
assert!(!ops.is_empty());
println!("Generated {} workflow operations", ops.len());
}
#[test]
fn test_parallel_workflow_with_join() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "parallel-processing", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("extract", "extract-text"))
.add_node(WorkflowNode::new("embed", "embedding").with_input("extract"))
.add_node(WorkflowNode::new("classify", "classifier").with_input("extract"))
.add_node(
WorkflowNode::new("join", "merge-results")
.with_input("embed")
.with_input("classify"),
)
.add_node(WorkflowNode::new("summarize", "summarization").with_input("join"))
.add_edge("extract", "embed")
.add_edge("extract", "classify")
.add_edge("embed", "join")
.add_edge("classify", "join")
.add_edge("join", "summarize");
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let instance_id = runtime
.create_instance(
workflow_ref.clone(),
"workflow-2",
vec![FlowRef::new("documents", "doc-1")],
)
.expect("Failed to create instance");
runtime
.start_instance(&instance_id)
.expect("Failed to start");
runtime
.claim_step(&instance_id, "extract", "peer-a")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "extract", FlowRef::new("texts", "text-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"embed".to_string()));
assert!(ready.contains(&"classify".to_string()));
assert!(!ready.contains(&"join".to_string()));
runtime
.claim_step(&instance_id, "embed", "peer-b")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "embed", FlowRef::new("embeddings", "embed-1"))
.expect("Failed to complete");
runtime
.claim_step(&instance_id, "classify", "peer-c")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "classify", FlowRef::new("classes", "class-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"join".to_string()));
runtime
.claim_step(&instance_id, "join", "peer-d")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "join", FlowRef::new("merged", "merged-1"))
.expect("Failed to complete");
runtime
.claim_step(&instance_id, "summarize", "peer-e")
.expect("Failed to claim");
runtime
.step_completed(
&instance_id,
"summarize",
FlowRef::new("summary", "summary-1"),
)
.expect("Failed to complete");
let instance = runtime.get_instance(&instance_id).unwrap();
assert_eq!(instance.state, crate::WorkflowState::Completed);
}
#[test]
fn test_workflow_with_human_intervention() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "approval-required", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("extract", "extract-text"))
.add_node(WorkflowNode::new("review", "manual-review").with_input("extract"))
.add_node(WorkflowNode::new("process", "process").with_input("review"));
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let instance_id = runtime
.create_instance(
workflow_ref.clone(),
"workflow-3",
vec![FlowRef::new("documents", "doc-1")],
)
.expect("Failed to create instance");
runtime
.start_instance(&instance_id)
.expect("Failed to start");
runtime
.claim_step(&instance_id, "extract", "peer-a")
.expect("Failed to claim");
runtime
.step_completed(&instance_id, "extract", FlowRef::new("texts", "text-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"review".to_string()));
runtime
.claim_step(&instance_id, "review", "peer-b")
.expect("Failed to claim");
runtime
.step_blocked(
&instance_id,
"review",
crate::BlockedReason::AwaitingApproval("Waiting for manager approval".to_string()),
)
.expect("Failed to block");
let ready = runtime.ready_steps(&instance_id);
assert!(!ready.contains(&"process".to_string()));
runtime
.unblock_step(&instance_id, "review")
.expect("Failed to unblock");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"review".to_string()));
runtime
.step_completed(&instance_id, "review", FlowRef::new("reviews", "review-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"process".to_string()));
}
#[test]
fn test_workflow_continues_on_peer_failure() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "failover-test", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("step1", "capability-a"))
.add_node(WorkflowNode::new("step2", "capability-b").with_input("step1"));
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let instance_id = runtime
.create_instance(workflow_ref.clone(), "workflow-4", Vec::new())
.expect("Failed to create instance");
runtime
.start_instance(&instance_id)
.expect("Failed to start");
runtime
.claim_step(&instance_id, "step1", "peer-a")
.expect("Failed to claim");
runtime
.step_executing(&instance_id, "step1")
.expect("Failed to mark executing");
let instance = runtime.get_instance(&instance_id).unwrap();
assert_eq!(
instance.get_step_state("step1"),
Some(crate::WorkflowStepState::Running)
);
runtime
.step_completed(&instance_id, "step1", FlowRef::new("results", "result-1"))
.expect("Failed to complete");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"step2".to_string()));
runtime
.claim_step(&instance_id, "step2", "peer-c")
.expect("Failed to claim by peer-c");
runtime
.step_completed(&instance_id, "step2", FlowRef::new("results", "result-2"))
.expect("Failed to complete step2");
let instance = runtime.get_instance(&instance_id).unwrap();
assert_eq!(instance.state, crate::WorkflowState::Completed);
}
#[test]
fn test_multiple_concurrent_workflows() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "multi-workflow", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("step1", "capability-a"))
.add_node(WorkflowNode::new("step2", "capability-b").with_input("step1"))
.add_edge("step1", "step2");
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let instance1 = runtime
.create_instance(workflow_ref.clone(), "wf-1", Vec::new())
.expect("Failed to create");
let instance2 = runtime
.create_instance(workflow_ref.clone(), "wf-2", Vec::new())
.expect("Failed to create");
let instance3 = runtime
.create_instance(workflow_ref.clone(), "wf-3", Vec::new())
.expect("Failed to create");
runtime.start_instance(&instance1).expect("Failed to start");
runtime.start_instance(&instance2).expect("Failed to start");
runtime.start_instance(&instance3).expect("Failed to start");
assert!(runtime
.ready_steps(&instance1)
.contains(&"step1".to_string()));
assert!(runtime
.ready_steps(&instance2)
.contains(&"step1".to_string()));
assert!(runtime
.ready_steps(&instance3)
.contains(&"step1".to_string()));
for (instance_id, peer) in &[
(&instance1, "peer-a"),
(&instance2, "peer-b"),
(&instance3, "peer-c"),
] {
runtime
.claim_step(*instance_id, "step1", *peer)
.expect("Failed to claim");
runtime
.step_completed(
*instance_id,
"step1",
FlowRef::new("results", &format!("result-{}", peer)),
)
.expect("Failed to complete");
runtime
.claim_step(*instance_id, "step2", *peer)
.expect("Failed to claim");
runtime
.step_completed(
*instance_id,
"step2",
FlowRef::new("results", &format!("final-{}", peer)),
)
.expect("Failed to complete");
}
assert_eq!(
runtime.get_instance(&instance1).unwrap().state,
crate::WorkflowState::Completed
);
assert_eq!(
runtime.get_instance(&instance2).unwrap().state,
crate::WorkflowState::Completed
);
assert_eq!(
runtime.get_instance(&instance3).unwrap().state,
crate::WorkflowState::Completed
);
}
#[test]
fn test_complex_dag_workflow() {
let mut runtime = WorkflowRuntime::new();
let workflow_ref = WorkflowRef::new("workflow", "complex-dag", 1);
let graph = WorkflowGraph::new(workflow_ref.clone())
.add_node(WorkflowNode::new("a", "cap-a"))
.add_node(WorkflowNode::new("b", "cap-b").with_input("a"))
.add_node(WorkflowNode::new("c", "cap-c").with_input("a"))
.add_node(
WorkflowNode::new("d", "cap-d")
.with_input("b")
.with_input("c"),
)
.add_node(WorkflowNode::new("e", "cap-e").with_input("d"))
.add_edge("a", "b")
.add_edge("a", "c")
.add_edge("b", "d")
.add_edge("c", "d")
.add_edge("d", "e");
runtime
.register_workflow(graph)
.expect("Failed to register workflow");
let instance_id = runtime
.create_instance(workflow_ref.clone(), "complex-1", Vec::new())
.expect("Failed to create");
runtime
.start_instance(&instance_id)
.expect("Failed to start");
assert!(runtime.ready_steps(&instance_id).contains(&"a".to_string()));
runtime
.claim_step(&instance_id, "a", "peer-1")
.expect("Failed");
runtime
.step_completed(&instance_id, "a", FlowRef::new("res", "a-result"))
.expect("Failed");
let ready = runtime.ready_steps(&instance_id);
assert!(ready.contains(&"b".to_string()));
assert!(ready.contains(&"c".to_string()));
runtime
.claim_step(&instance_id, "b", "peer-2")
.expect("Failed");
runtime
.step_completed(&instance_id, "b", FlowRef::new("res", "b-result"))
.expect("Failed");
assert!(!runtime.ready_steps(&instance_id).contains(&"d".to_string()));
runtime
.claim_step(&instance_id, "c", "peer-3")
.expect("Failed");
runtime
.step_completed(&instance_id, "c", FlowRef::new("res", "c-result"))
.expect("Failed");
assert!(runtime.ready_steps(&instance_id).contains(&"d".to_string()));
runtime
.claim_step(&instance_id, "d", "peer-4")
.expect("Failed");
runtime
.step_completed(&instance_id, "d", FlowRef::new("res", "d-result"))
.expect("Failed");
runtime
.claim_step(&instance_id, "e", "peer-5")
.expect("Failed");
runtime
.step_completed(&instance_id, "e", FlowRef::new("res", "e-result"))
.expect("Failed");
let instance = runtime.get_instance(&instance_id).unwrap();
assert_eq!(instance.state, crate::WorkflowState::Completed);
}
}