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:
Shunsuke Hayashi 2025-11-23 00:28:39 +09:00
parent 5cfd656dbc
commit 364dfd9da3
2 changed files with 461 additions and 0 deletions

View file

@ -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);
}
}

View file

@ -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));
}
}