test: Add comprehensive Workflow and DAG tests
Workflow tests (14 new tests): - Test StepCondition, RetryConfig, FailurePolicy defaults - Test StepStatus and WorkflowStatus equality - Test WorkflowContext with variables and results - Test StepResult and WorkflowResult creation - Test Workflow with output variables - Test step and failure policy variants DAG tests (23 new tests): - Test TaskId creation and display - Test Task with priority and estimated time - Test TaskNode operations (add/remove dependencies) - Test TaskLevel creation and management - Test TaskGraph operations (count, parallelism, levels) - Test TaskGraphBuilder with priority - Test DAGError variants - Test complex parallel graphs Total: 730 tests passing (228 core, 498 TUI, 4 CLI) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5cfd656dbc
commit
364dfd9da3
2 changed files with 461 additions and 0 deletions
|
|
@ -559,4 +559,265 @@ mod tests {
|
|||
assert!(roots.contains(&id1));
|
||||
assert!(roots.contains(&id2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_id_default() {
|
||||
let id = TaskId::default();
|
||||
assert!(!id.to_string().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_with_priority() {
|
||||
let task = Task::new("priority-task", "Task with priority")
|
||||
.with_priority(10);
|
||||
|
||||
assert_eq!(task.priority, 10);
|
||||
assert_eq!(task.name, "priority-task");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_with_estimated_time() {
|
||||
let task = Task::new("timed-task", "Task with time estimate")
|
||||
.with_estimated_time(120);
|
||||
|
||||
assert_eq!(task.estimated_time, Some(120));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_node_is_root() {
|
||||
let task = Task::new("root-task", "Root task");
|
||||
let node = TaskNode::new(task);
|
||||
|
||||
assert!(node.is_root());
|
||||
assert_eq!(node.indegree(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_node_add_dependency() {
|
||||
let task1 = Task::new("task1", "First task");
|
||||
let task2 = Task::new("task2", "Second task");
|
||||
let mut node = TaskNode::new(task1);
|
||||
let dep_id = task2.id;
|
||||
|
||||
node.add_dependency(dep_id);
|
||||
assert!(!node.is_root());
|
||||
assert_eq!(node.indegree(), 1);
|
||||
assert!(node.dependencies.contains(&dep_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_node_add_dependent() {
|
||||
let task1 = Task::new("task1", "First task");
|
||||
let task2 = Task::new("task2", "Second task");
|
||||
let mut node = TaskNode::new(task1);
|
||||
let dependent_id = task2.id;
|
||||
|
||||
node.add_dependent(dependent_id);
|
||||
assert!(node.dependents.contains(&dependent_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_node_no_duplicate_dependencies() {
|
||||
let task1 = Task::new("task1", "First task");
|
||||
let task2 = Task::new("task2", "Second task");
|
||||
let mut node = TaskNode::new(task1);
|
||||
let dep_id = task2.id;
|
||||
|
||||
node.add_dependency(dep_id);
|
||||
node.add_dependency(dep_id); // Add again
|
||||
|
||||
assert_eq!(node.dependencies.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_level_creation() {
|
||||
let level = TaskLevel::new(0);
|
||||
assert_eq!(level.level, 0);
|
||||
assert!(level.is_empty());
|
||||
assert_eq!(level.task_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_level_add_task() {
|
||||
let mut level = TaskLevel::new(1);
|
||||
let task = Task::new("task1", "First task");
|
||||
|
||||
level.add_task(task);
|
||||
assert!(!level.is_empty());
|
||||
assert_eq!(level.task_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_task_count() {
|
||||
let mut graph = TaskGraph::new(4);
|
||||
assert_eq!(graph.task_count(), 0);
|
||||
|
||||
let task = Task::new("task", "Test task");
|
||||
graph.add_task(task);
|
||||
assert_eq!(graph.task_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_max_parallelism() {
|
||||
let graph = TaskGraph::new(8);
|
||||
assert_eq!(graph.max_parallelism(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_get_node() {
|
||||
let mut graph = TaskGraph::new(4);
|
||||
let task = Task::new("test", "Test task");
|
||||
let id = graph.add_task(task);
|
||||
|
||||
let node = graph.get_node(&id);
|
||||
assert!(node.is_some());
|
||||
assert_eq!(node.unwrap().task.name, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_task_ids() {
|
||||
let mut graph = TaskGraph::new(4);
|
||||
let task1 = Task::new("task1", "First");
|
||||
let task2 = Task::new("task2", "Second");
|
||||
|
||||
let id1 = graph.add_task(task1);
|
||||
let id2 = graph.add_task(task2);
|
||||
|
||||
let ids = graph.task_ids();
|
||||
assert_eq!(ids.len(), 2);
|
||||
assert!(ids.contains(&id1));
|
||||
assert!(ids.contains(&id2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_levels() {
|
||||
let mut graph = TaskGraph::new(4);
|
||||
|
||||
let task1 = Task::new("task1", "First");
|
||||
let task2 = Task::new("task2", "Second");
|
||||
|
||||
let id1 = graph.add_task(task1);
|
||||
let id2 = graph.add_task(task2);
|
||||
|
||||
graph.add_dependency(id2, id1).unwrap();
|
||||
graph.build_levels().unwrap();
|
||||
|
||||
let levels = graph.levels();
|
||||
assert!(!levels.is_empty());
|
||||
// Tasks are added to levels as their dependencies are satisfied
|
||||
assert!(graph.level_count() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_with_priority() {
|
||||
let graph = TaskGraphBuilder::new(4)
|
||||
.add_task_with_priority("high-priority", "Important task", 100)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(graph.task_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_task_not_found() {
|
||||
let error = DAGError::TaskNotFound("missing-task".to_string());
|
||||
let display = format!("{}", error);
|
||||
assert!(display.contains("missing-task"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_circular_dependency() {
|
||||
let error = DAGError::CircularDependency("cycle detected".to_string());
|
||||
let display = format!("{}", error);
|
||||
assert!(display.contains("cycle"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dag_error_invalid_graph() {
|
||||
let error = DAGError::InvalidGraph("invalid".to_string());
|
||||
let display = format!("{}", error);
|
||||
assert!(display.contains("invalid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_parallel_graph() {
|
||||
// Create a graph with multiple parallel paths
|
||||
// Root -> [A, B, C] -> Merge
|
||||
let mut graph = TaskGraph::new(4);
|
||||
|
||||
let root = Task::new("root", "Root task");
|
||||
let a = Task::new("a", "Task A");
|
||||
let b = Task::new("b", "Task B");
|
||||
let c = Task::new("c", "Task C");
|
||||
let merge = Task::new("merge", "Merge task");
|
||||
|
||||
let root_id = graph.add_task(root);
|
||||
let a_id = graph.add_task(a);
|
||||
let b_id = graph.add_task(b);
|
||||
let c_id = graph.add_task(c);
|
||||
let merge_id = graph.add_task(merge);
|
||||
|
||||
graph.add_dependency(a_id, root_id).unwrap();
|
||||
graph.add_dependency(b_id, root_id).unwrap();
|
||||
graph.add_dependency(c_id, root_id).unwrap();
|
||||
graph.add_dependency(merge_id, a_id).unwrap();
|
||||
graph.add_dependency(merge_id, b_id).unwrap();
|
||||
graph.add_dependency(merge_id, c_id).unwrap();
|
||||
|
||||
graph.build_levels().unwrap();
|
||||
|
||||
// All 5 tasks should be assigned to levels
|
||||
assert!(graph.level_count() >= 1);
|
||||
let total_tasks: usize = graph.levels().iter().map(|l| l.task_count()).sum();
|
||||
assert_eq!(total_tasks, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_dependency_error() {
|
||||
let result = TaskGraphBuilder::new(4)
|
||||
.add_task("task1", "First")
|
||||
.add_dependency("task1", "nonexistent");
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_task_graph() {
|
||||
let mut graph = TaskGraph::new(4);
|
||||
let task = Task::new("single", "Single task");
|
||||
graph.add_task(task);
|
||||
|
||||
graph.build_levels().unwrap();
|
||||
|
||||
assert_eq!(graph.level_count(), 1);
|
||||
assert_eq!(graph.levels()[0].task_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_id_display() {
|
||||
let id = TaskId::new();
|
||||
let display = id.to_string();
|
||||
|
||||
// Should be a valid UUID string
|
||||
assert!(!display.is_empty());
|
||||
assert!(display.contains('-')); // UUIDs contain hyphens
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_independent_tasks_parallel() {
|
||||
// All independent tasks should be in the same level
|
||||
let mut graph = TaskGraph::new(10);
|
||||
|
||||
for i in 0..5 {
|
||||
let task = Task::new(format!("task{}", i), format!("Task {}", i));
|
||||
graph.add_task(task);
|
||||
}
|
||||
|
||||
graph.build_levels().unwrap();
|
||||
|
||||
// All tasks are independent, so they should be in level 0
|
||||
// (with max_parallelism=10, they can all run in parallel)
|
||||
assert_eq!(graph.level_count(), 1);
|
||||
assert_eq!(graph.levels()[0].task_count(), 5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -691,4 +691,204 @@ mod tests {
|
|||
assert_eq!(result.status, WorkflowStatus::Completed);
|
||||
assert_eq!(result.steps.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_condition_default() {
|
||||
let condition = StepCondition::default();
|
||||
assert!(matches!(condition, StepCondition::Always));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retry_config_default() {
|
||||
let config = RetryConfig::default();
|
||||
assert_eq!(config.max_attempts, 1);
|
||||
assert_eq!(config.delay, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failure_policy_default() {
|
||||
let policy = FailurePolicy::default();
|
||||
assert!(matches!(policy, FailurePolicy::Stop));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_status_equality() {
|
||||
assert_eq!(StepStatus::Pending, StepStatus::Pending);
|
||||
assert_eq!(StepStatus::Completed, StepStatus::Completed);
|
||||
assert_ne!(StepStatus::Pending, StepStatus::Completed);
|
||||
assert_ne!(StepStatus::Failed, StepStatus::Skipped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_status_equality() {
|
||||
assert_eq!(WorkflowStatus::Completed, WorkflowStatus::Completed);
|
||||
assert_ne!(WorkflowStatus::Completed, WorkflowStatus::Failed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_context_with_variables() {
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("key1".to_string(), "value1".to_string());
|
||||
vars.insert("key2".to_string(), "value2".to_string());
|
||||
|
||||
let context = WorkflowContext::new().with_variables(vars);
|
||||
assert_eq!(context.get_variable("key1"), Some(&"value1".to_string()));
|
||||
assert_eq!(context.get_variable("key2"), Some(&"value2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_context_add_result() {
|
||||
let mut context = WorkflowContext::new();
|
||||
let result = StepResult {
|
||||
step_id: "test-step".to_string(),
|
||||
status: StepStatus::Completed,
|
||||
output: Some("output".to_string()),
|
||||
error: None,
|
||||
duration_ms: 100,
|
||||
};
|
||||
|
||||
context.add_result(result);
|
||||
let retrieved = context.get_result("test-step");
|
||||
assert!(retrieved.is_some());
|
||||
assert_eq!(retrieved.unwrap().status, StepStatus::Completed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_result_creation() {
|
||||
let result = StepResult {
|
||||
step_id: "test-step".to_string(),
|
||||
status: StepStatus::Completed,
|
||||
output: Some("test output".to_string()),
|
||||
error: None,
|
||||
duration_ms: 150,
|
||||
};
|
||||
|
||||
assert_eq!(result.step_id, "test-step");
|
||||
assert_eq!(result.status, StepStatus::Completed);
|
||||
assert_eq!(result.output, Some("test output".to_string()));
|
||||
assert!(result.error.is_none());
|
||||
assert_eq!(result.duration_ms, 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_result_creation() {
|
||||
let result = WorkflowResult {
|
||||
workflow_name: "test-workflow".to_string(),
|
||||
status: WorkflowStatus::Completed,
|
||||
steps: vec![],
|
||||
duration_ms: 1000,
|
||||
};
|
||||
|
||||
assert_eq!(result.workflow_name, "test-workflow");
|
||||
assert_eq!(result.status, WorkflowStatus::Completed);
|
||||
assert!(result.steps.is_empty());
|
||||
assert_eq!(result.duration_ms, 1000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_with_output_variables() {
|
||||
let mut manager = WorkflowManager::new();
|
||||
let workflow = Workflow {
|
||||
name: "output-test".to_string(),
|
||||
description: "Test output variables".to_string(),
|
||||
steps: vec![
|
||||
WorkflowStep {
|
||||
id: "step1".to_string(),
|
||||
name: "Step 1".to_string(),
|
||||
agent: None,
|
||||
task: "First task".to_string(),
|
||||
depends_on: vec![],
|
||||
condition: None,
|
||||
timeout: 30,
|
||||
retry: RetryConfig::default(),
|
||||
output: Some("first_result".to_string()),
|
||||
},
|
||||
WorkflowStep {
|
||||
id: "step2".to_string(),
|
||||
name: "Step 2".to_string(),
|
||||
agent: None,
|
||||
task: "Use ${first_result}".to_string(),
|
||||
depends_on: vec!["step1".to_string()],
|
||||
condition: None,
|
||||
timeout: 30,
|
||||
retry: RetryConfig::default(),
|
||||
output: None,
|
||||
},
|
||||
],
|
||||
variables: HashMap::new(),
|
||||
on_failure: FailurePolicy::Stop,
|
||||
};
|
||||
|
||||
manager.register(workflow);
|
||||
let result = manager.execute("output-test", HashMap::new()).await.unwrap();
|
||||
|
||||
assert_eq!(result.status, WorkflowStatus::Completed);
|
||||
assert_eq!(result.steps.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_with_global_variables() {
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("project".to_string(), "miyabi".to_string());
|
||||
|
||||
let workflow = Workflow {
|
||||
name: "vars-test".to_string(),
|
||||
description: "Test global variables".to_string(),
|
||||
steps: vec![],
|
||||
variables: vars,
|
||||
on_failure: FailurePolicy::Stop,
|
||||
};
|
||||
|
||||
assert_eq!(workflow.variables.get("project"), Some(&"miyabi".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workflow_step_with_all_fields() {
|
||||
let step = WorkflowStep {
|
||||
id: "complete-step".to_string(),
|
||||
name: "Complete Step".to_string(),
|
||||
agent: Some("test-agent".to_string()),
|
||||
task: "Complete task".to_string(),
|
||||
depends_on: vec!["dep1".to_string(), "dep2".to_string()],
|
||||
condition: Some(StepCondition::OnSuccess),
|
||||
timeout: 600,
|
||||
retry: RetryConfig {
|
||||
max_attempts: 3,
|
||||
delay: 10,
|
||||
},
|
||||
output: Some("result".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(step.id, "complete-step");
|
||||
assert_eq!(step.agent, Some("test-agent".to_string()));
|
||||
assert_eq!(step.depends_on.len(), 2);
|
||||
assert_eq!(step.timeout, 600);
|
||||
assert_eq!(step.retry.max_attempts, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_condition_variants() {
|
||||
let always = StepCondition::Always;
|
||||
let on_success = StepCondition::OnSuccess;
|
||||
let on_failure = StepCondition::OnFailure;
|
||||
let if_expr = StepCondition::If {
|
||||
expression: "true".to_string(),
|
||||
};
|
||||
|
||||
assert!(matches!(always, StepCondition::Always));
|
||||
assert!(matches!(on_success, StepCondition::OnSuccess));
|
||||
assert!(matches!(on_failure, StepCondition::OnFailure));
|
||||
assert!(matches!(if_expr, StepCondition::If { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failure_policy_variants() {
|
||||
let stop = FailurePolicy::Stop;
|
||||
let continue_policy = FailurePolicy::Continue;
|
||||
let cleanup = FailurePolicy::Cleanup;
|
||||
|
||||
assert!(matches!(stop, FailurePolicy::Stop));
|
||||
assert!(matches!(continue_policy, FailurePolicy::Continue));
|
||||
assert!(matches!(cleanup, FailurePolicy::Cleanup));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue