intent_engine/
plan.rs

1//! Plan Interface - Declarative Task Management
2//!
3//! Provides a declarative API for creating and updating task structures,
4//! inspired by TodoWrite pattern. Simplifies complex operations into
5//! single atomic calls.
6
7use serde::{Deserialize, Serialize};
8use sqlx::Row;
9use std::collections::HashMap;
10
11/// Request for creating/updating task structure declaratively
12#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
13pub struct PlanRequest {
14    /// Task tree to create or update
15    pub tasks: Vec<TaskTree>,
16}
17
18/// Hierarchical task definition with nested children
19#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
20pub struct TaskTree {
21    /// Task name (used as identifier for lookups)
22    pub name: String,
23
24    /// Optional task specification/description
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub spec: Option<String>,
27
28    /// Optional priority level
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub priority: Option<PriorityValue>,
31
32    /// Nested child tasks (direct hierarchy expression)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub children: Option<Vec<TaskTree>>,
35
36    /// Task dependencies by name (name-based references)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub depends_on: Option<Vec<String>>,
39
40    /// Optional explicit task ID (for forced updates)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub task_id: Option<i64>,
43
44    /// Optional task status (for TodoWriter compatibility)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub status: Option<TaskStatus>,
47
48    /// Optional active form description (for TodoWriter compatibility)
49    /// Used for UI display when task is in_progress
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub active_form: Option<String>,
52}
53
54/// Task status for workflow management
55#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
56#[serde(rename_all = "snake_case")]
57pub enum TaskStatus {
58    Todo,
59    Doing,
60    Done,
61}
62
63impl TaskStatus {
64    /// Convert to database string representation
65    pub fn as_db_str(&self) -> &'static str {
66        match self {
67            TaskStatus::Todo => "todo",
68            TaskStatus::Doing => "doing",
69            TaskStatus::Done => "done",
70        }
71    }
72
73    /// Create from database string representation
74    pub fn from_db_str(s: &str) -> Option<Self> {
75        match s {
76            "todo" => Some(TaskStatus::Todo),
77            "doing" => Some(TaskStatus::Doing),
78            "done" => Some(TaskStatus::Done),
79            _ => None,
80        }
81    }
82
83    /// Convert to string representation for JSON API
84    pub fn as_str(&self) -> &'static str {
85        match self {
86            TaskStatus::Todo => "todo",
87            TaskStatus::Doing => "doing",
88            TaskStatus::Done => "done",
89        }
90    }
91}
92
93/// Priority value as string enum for JSON API
94#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
95#[serde(rename_all = "lowercase")]
96pub enum PriorityValue {
97    Critical,
98    High,
99    Medium,
100    Low,
101}
102
103impl PriorityValue {
104    /// Convert to integer representation for database storage
105    pub fn to_int(&self) -> i32 {
106        match self {
107            PriorityValue::Critical => 1,
108            PriorityValue::High => 2,
109            PriorityValue::Medium => 3,
110            PriorityValue::Low => 4,
111        }
112    }
113
114    /// Create from integer representation
115    pub fn from_int(value: i32) -> Option<Self> {
116        match value {
117            1 => Some(PriorityValue::Critical),
118            2 => Some(PriorityValue::High),
119            3 => Some(PriorityValue::Medium),
120            4 => Some(PriorityValue::Low),
121            _ => None,
122        }
123    }
124
125    /// Convert to string representation
126    pub fn as_str(&self) -> &'static str {
127        match self {
128            PriorityValue::Critical => "critical",
129            PriorityValue::High => "high",
130            PriorityValue::Medium => "medium",
131            PriorityValue::Low => "low",
132        }
133    }
134}
135
136/// Result of plan execution
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
138pub struct PlanResult {
139    /// Whether the operation succeeded
140    pub success: bool,
141
142    /// Mapping of task names to their IDs (for reference)
143    pub task_id_map: HashMap<String, i64>,
144
145    /// Number of tasks created
146    pub created_count: usize,
147
148    /// Number of tasks updated
149    pub updated_count: usize,
150
151    /// Number of dependencies created
152    pub dependency_count: usize,
153
154    /// Optional error message if success = false
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub error: Option<String>,
157}
158
159impl PlanResult {
160    /// Create a successful result
161    pub fn success(
162        task_id_map: HashMap<String, i64>,
163        created_count: usize,
164        updated_count: usize,
165        dependency_count: usize,
166    ) -> Self {
167        Self {
168            success: true,
169            task_id_map,
170            created_count,
171            updated_count,
172            dependency_count,
173            error: None,
174        }
175    }
176
177    /// Create an error result
178    pub fn error(message: impl Into<String>) -> Self {
179        Self {
180            success: false,
181            task_id_map: HashMap::new(),
182            created_count: 0,
183            updated_count: 0,
184            dependency_count: 0,
185            error: Some(message.into()),
186        }
187    }
188}
189
190// ============================================================================
191// Name Extraction and Classification Logic
192// ============================================================================
193
194/// Extract all task names from a task tree (recursive)
195pub fn extract_all_names(tasks: &[TaskTree]) -> Vec<String> {
196    let mut names = Vec::new();
197
198    for task in tasks {
199        names.push(task.name.clone());
200
201        if let Some(children) = &task.children {
202            names.extend(extract_all_names(children));
203        }
204    }
205
206    names
207}
208
209/// Flatten task tree into a linear list with parent information
210#[derive(Debug, Clone, PartialEq)]
211pub struct FlatTask {
212    pub name: String,
213    pub spec: Option<String>,
214    pub priority: Option<PriorityValue>,
215    pub parent_name: Option<String>,
216    pub depends_on: Vec<String>,
217    pub task_id: Option<i64>,
218    pub status: Option<TaskStatus>,
219    pub active_form: Option<String>,
220}
221
222pub fn flatten_task_tree(tasks: &[TaskTree]) -> Vec<FlatTask> {
223    flatten_task_tree_recursive(tasks, None)
224}
225
226fn flatten_task_tree_recursive(tasks: &[TaskTree], parent_name: Option<String>) -> Vec<FlatTask> {
227    let mut flat = Vec::new();
228
229    for task in tasks {
230        let flat_task = FlatTask {
231            name: task.name.clone(),
232            spec: task.spec.clone(),
233            priority: task.priority.clone(),
234            parent_name: parent_name.clone(),
235            depends_on: task.depends_on.clone().unwrap_or_default(),
236            task_id: task.task_id,
237            status: task.status.clone(),
238            active_form: task.active_form.clone(),
239        };
240
241        flat.push(flat_task);
242
243        // Recursively flatten children
244        if let Some(children) = &task.children {
245            flat.extend(flatten_task_tree_recursive(
246                children,
247                Some(task.name.clone()),
248            ));
249        }
250    }
251
252    flat
253}
254
255/// Operation classification result
256#[derive(Debug, Clone, PartialEq)]
257pub enum Operation {
258    Create(FlatTask),
259    Update { task_id: i64, task: FlatTask },
260}
261
262/// Classify tasks into create/update operations based on existing task IDs
263///
264/// # Arguments
265/// * `flat_tasks` - Flattened task list
266/// * `existing_names` - Map of existing task names to their IDs
267///
268/// # Returns
269/// Classified operations (create or update)
270pub fn classify_operations(
271    flat_tasks: &[FlatTask],
272    existing_names: &HashMap<String, i64>,
273) -> Vec<Operation> {
274    let mut operations = Vec::new();
275
276    for task in flat_tasks {
277        // Priority: explicit task_id > name lookup > create
278        let operation = if let Some(task_id) = task.task_id {
279            // Explicit task_id → forced update
280            Operation::Update {
281                task_id,
282                task: task.clone(),
283            }
284        } else if let Some(&task_id) = existing_names.get(&task.name) {
285            // Name found in DB → update
286            Operation::Update {
287                task_id,
288                task: task.clone(),
289            }
290        } else {
291            // Not found → create
292            Operation::Create(task.clone())
293        };
294
295        operations.push(operation);
296    }
297
298    operations
299}
300
301/// Find duplicate names in a task list
302pub fn find_duplicate_names(tasks: &[TaskTree]) -> Vec<String> {
303    let mut seen = HashMap::new();
304    let mut duplicates = Vec::new();
305
306    for name in extract_all_names(tasks) {
307        let count = seen.entry(name.clone()).or_insert(0);
308        *count += 1;
309        if *count == 2 {
310            // Only add once when we first detect the duplicate
311            duplicates.push(name);
312        }
313    }
314
315    duplicates
316}
317
318// ============================================================================
319// Database Operations (Plan Executor)
320// ============================================================================
321
322use crate::error::{IntentError, Result};
323use chrono::Utc;
324use sqlx::SqlitePool;
325
326/// Plan executor for creating/updating task structures
327pub struct PlanExecutor<'a> {
328    pool: &'a SqlitePool,
329}
330
331impl<'a> PlanExecutor<'a> {
332    /// Create a new plan executor
333    pub fn new(pool: &'a SqlitePool) -> Self {
334        Self { pool }
335    }
336
337    /// Execute a plan request (Phase 2: create + update mode)
338    pub async fn execute(&self, request: &PlanRequest) -> Result<PlanResult> {
339        // 1. Check for duplicate names in the request
340        let duplicates = find_duplicate_names(&request.tasks);
341        if !duplicates.is_empty() {
342            return Ok(PlanResult::error(format!(
343                "Duplicate task names in request: {:?}",
344                duplicates
345            )));
346        }
347
348        // 2. Extract all task names
349        let all_names = extract_all_names(&request.tasks);
350
351        // 3. Find existing tasks by name
352        let existing = self.find_tasks_by_names(&all_names).await?;
353
354        // 4. Flatten the task tree
355        let flat_tasks = flatten_task_tree(&request.tasks);
356
357        // 5. Validate dependencies exist in the plan
358        if let Err(e) = self.validate_dependencies(&flat_tasks) {
359            return Ok(PlanResult::error(e.to_string()));
360        }
361
362        // 6. Detect circular dependencies
363        if let Err(e) = self.detect_circular_dependencies(&flat_tasks) {
364            return Ok(PlanResult::error(e.to_string()));
365        }
366
367        // 7. Validate batch-level single doing constraint
368        if let Err(e) = self.validate_batch_single_doing(&flat_tasks) {
369            return Ok(PlanResult::error(e.to_string()));
370        }
371
372        // 8. Execute in transaction
373        let mut tx = self.pool.begin().await?;
374
375        // 8. Create or update tasks based on existence
376        let mut task_id_map = HashMap::new();
377        let mut created_count = 0;
378        let mut updated_count = 0;
379
380        for task in &flat_tasks {
381            if let Some(&existing_id) = existing.get(&task.name) {
382                // Task exists -> UPDATE
383                self.update_task_in_tx(&mut tx, existing_id, task).await?;
384                task_id_map.insert(task.name.clone(), existing_id);
385                updated_count += 1;
386            } else {
387                // Task doesn't exist -> CREATE
388                let id = self.create_task_in_tx(&mut tx, task).await?;
389                task_id_map.insert(task.name.clone(), id);
390                created_count += 1;
391            }
392        }
393
394        // 9. Build parent-child relationships
395        self.build_parent_child_relations(&mut tx, &flat_tasks, &task_id_map)
396            .await?;
397
398        // 10. Build dependencies
399        let dep_count = self
400            .build_dependencies(&mut tx, &flat_tasks, &task_id_map)
401            .await?;
402
403        // 11. Commit transaction
404        tx.commit().await?;
405
406        // 12. Auto-focus the doing task if present
407        // Find the doing task in the batch
408        let doing_task = flat_tasks
409            .iter()
410            .find(|task| matches!(task.status, Some(TaskStatus::Doing)));
411
412        if let Some(doing_task) = doing_task {
413            // Get the task ID from the map
414            if let Some(&task_id) = task_id_map.get(&doing_task.name) {
415                // Call task_start to set focus (inherits doing status)
416                use crate::tasks::TaskManager;
417                let task_mgr = TaskManager::new(self.pool);
418                task_mgr.start_task(task_id, false).await?;
419            }
420        }
421
422        // 13. Return success result
423        Ok(PlanResult::success(
424            task_id_map,
425            created_count,
426            updated_count,
427            dep_count,
428        ))
429    }
430
431    /// Find tasks by names
432    async fn find_tasks_by_names(&self, names: &[String]) -> Result<HashMap<String, i64>> {
433        if names.is_empty() {
434            return Ok(HashMap::new());
435        }
436
437        let mut map = HashMap::new();
438
439        // Query all names at once using IN clause
440        // Build placeholders: ?, ?, ?...
441        let placeholders = names.iter().map(|_| "?").collect::<Vec<_>>().join(",");
442        let query = format!(
443            "SELECT id, name FROM tasks WHERE name IN ({})",
444            placeholders
445        );
446
447        let mut query_builder = sqlx::query(&query);
448        for name in names {
449            query_builder = query_builder.bind(name);
450        }
451
452        let rows = query_builder.fetch_all(self.pool).await?;
453
454        for row in rows {
455            let id: i64 = row.get("id");
456            let name: String = row.get("name");
457            map.insert(name, id);
458        }
459
460        Ok(map)
461    }
462
463    /// Create a single task in a transaction
464    async fn create_task_in_tx(
465        &self,
466        tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
467        task: &FlatTask,
468    ) -> Result<i64> {
469        let now = Utc::now();
470        let priority = task.priority.as_ref().map(|p| p.to_int()).unwrap_or(3); // Default: medium
471
472        // Determine status - use provided status or default to 'todo'
473        let status_str = match &task.status {
474            Some(status) => status.as_db_str(),
475            None => "todo",
476        };
477
478        let result = sqlx::query(
479            r#"
480            INSERT INTO tasks (name, spec, priority, status, active_form, first_todo_at)
481            VALUES (?, ?, ?, ?, ?, ?)
482            "#,
483        )
484        .bind(&task.name)
485        .bind(&task.spec)
486        .bind(priority)
487        .bind(status_str)
488        .bind(&task.active_form)
489        .bind(now)
490        .execute(&mut **tx)
491        .await?;
492
493        Ok(result.last_insert_rowid())
494    }
495
496    /// Update a single task in a transaction (Phase 2)
497    /// Only updates non-None fields to support partial updates
498    async fn update_task_in_tx(
499        &self,
500        tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
501        task_id: i64,
502        task: &FlatTask,
503    ) -> Result<()> {
504        // Update spec if provided
505        if let Some(spec) = &task.spec {
506            sqlx::query("UPDATE tasks SET spec = ? WHERE id = ?")
507                .bind(spec)
508                .bind(task_id)
509                .execute(&mut **tx)
510                .await?;
511        }
512
513        // Update priority if provided
514        if let Some(priority) = &task.priority {
515            sqlx::query("UPDATE tasks SET priority = ? WHERE id = ?")
516                .bind(priority.to_int())
517                .bind(task_id)
518                .execute(&mut **tx)
519                .await?;
520        }
521
522        // Update status if provided (for TodoWriter compatibility)
523        if let Some(status) = &task.status {
524            sqlx::query("UPDATE tasks SET status = ? WHERE id = ?")
525                .bind(status.as_db_str())
526                .bind(task_id)
527                .execute(&mut **tx)
528                .await?;
529        }
530
531        // Update active_form if provided (for TodoWriter compatibility)
532        if let Some(active_form) = &task.active_form {
533            sqlx::query("UPDATE tasks SET active_form = ? WHERE id = ?")
534                .bind(active_form)
535                .bind(task_id)
536                .execute(&mut **tx)
537                .await?;
538        }
539
540        // Note: We don't update name or timestamps
541        // - name: Used for identity, changing it would break references
542        // - timestamps: Should preserve original creation time
543
544        Ok(())
545    }
546
547    /// Build parent-child relationships
548    async fn build_parent_child_relations(
549        &self,
550        tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
551        flat_tasks: &[FlatTask],
552        task_id_map: &HashMap<String, i64>,
553    ) -> Result<()> {
554        for task in flat_tasks {
555            if let Some(parent_name) = &task.parent_name {
556                let task_id = task_id_map.get(&task.name).ok_or_else(|| {
557                    IntentError::InvalidInput(format!("Task not found: {}", task.name))
558                })?;
559
560                let parent_id = task_id_map.get(parent_name).ok_or_else(|| {
561                    IntentError::InvalidInput(format!("Parent task not found: {}", parent_name))
562                })?;
563
564                sqlx::query("UPDATE tasks SET parent_id = ? WHERE id = ?")
565                    .bind(parent_id)
566                    .bind(task_id)
567                    .execute(&mut **tx)
568                    .await?;
569            }
570        }
571
572        Ok(())
573    }
574
575    /// Build dependency relationships
576    async fn build_dependencies(
577        &self,
578        tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
579        flat_tasks: &[FlatTask],
580        task_id_map: &HashMap<String, i64>,
581    ) -> Result<usize> {
582        let mut count = 0;
583
584        for task in flat_tasks {
585            if !task.depends_on.is_empty() {
586                let blocked_id = task_id_map.get(&task.name).ok_or_else(|| {
587                    IntentError::InvalidInput(format!("Task not found: {}", task.name))
588                })?;
589
590                for dep_name in &task.depends_on {
591                    let blocking_id = task_id_map.get(dep_name).ok_or_else(|| {
592                        IntentError::InvalidInput(format!(
593                            "Dependency '{}' not found for task '{}'",
594                            dep_name, task.name
595                        ))
596                    })?;
597
598                    sqlx::query(
599                        "INSERT INTO dependencies (blocking_task_id, blocked_task_id) VALUES (?, ?)",
600                    )
601                    .bind(blocking_id)
602                    .bind(blocked_id)
603                    .execute(&mut **tx)
604                    .await?;
605
606                    count += 1;
607                }
608            }
609        }
610
611        Ok(count)
612    }
613
614    /// Validate that all dependencies exist in the plan
615    fn validate_dependencies(&self, flat_tasks: &[FlatTask]) -> Result<()> {
616        let task_names: std::collections::HashSet<_> =
617            flat_tasks.iter().map(|t| t.name.as_str()).collect();
618
619        for task in flat_tasks {
620            for dep_name in &task.depends_on {
621                if !task_names.contains(dep_name.as_str()) {
622                    return Err(IntentError::InvalidInput(format!(
623                        "Task '{}' depends on '{}', but '{}' is not in the plan",
624                        task.name, dep_name, dep_name
625                    )));
626                }
627            }
628        }
629
630        Ok(())
631    }
632
633    /// Validate batch-level single doing constraint
634    /// Ensures only one task in the request batch can have status='doing'
635    /// (Database can have multiple 'doing' tasks to support hierarchical workflows)
636    fn validate_batch_single_doing(&self, flat_tasks: &[FlatTask]) -> Result<()> {
637        // Find all tasks in the request that want to be doing
638        let doing_tasks: Vec<&FlatTask> = flat_tasks
639            .iter()
640            .filter(|task| matches!(task.status, Some(TaskStatus::Doing)))
641            .collect();
642
643        // If more than one task in the request wants to be doing, that's an error
644        if doing_tasks.len() > 1 {
645            let names: Vec<&str> = doing_tasks.iter().map(|t| t.name.as_str()).collect();
646            return Err(IntentError::InvalidInput(format!(
647                "Batch single doing constraint violated: only one task per batch can have status='doing'. Found: {}",
648                names.join(", ")
649            )));
650        }
651
652        Ok(())
653    }
654
655    /// Detect circular dependencies using Tarjan's algorithm for strongly connected components
656    fn detect_circular_dependencies(&self, flat_tasks: &[FlatTask]) -> Result<()> {
657        if flat_tasks.is_empty() {
658            return Ok(());
659        }
660
661        // Build name-to-index mapping
662        let name_to_idx: HashMap<&str, usize> = flat_tasks
663            .iter()
664            .enumerate()
665            .map(|(i, t)| (t.name.as_str(), i))
666            .collect();
667
668        // Build dependency graph (adjacency list)
669        let mut graph: Vec<Vec<usize>> = vec![Vec::new(); flat_tasks.len()];
670        for (idx, task) in flat_tasks.iter().enumerate() {
671            for dep_name in &task.depends_on {
672                if let Some(&dep_idx) = name_to_idx.get(dep_name.as_str()) {
673                    graph[idx].push(dep_idx);
674                }
675            }
676        }
677
678        // Check for self-loops first
679        for task in flat_tasks {
680            if task.depends_on.contains(&task.name) {
681                return Err(IntentError::InvalidInput(format!(
682                    "Circular dependency detected: task '{}' depends on itself",
683                    task.name
684                )));
685            }
686        }
687
688        // Run Tarjan's SCC algorithm
689        let sccs = self.tarjan_scc(&graph);
690
691        // Check for cycles (any SCC with size > 1)
692        for scc in sccs {
693            if scc.len() > 1 {
694                // Found a cycle - build error message
695                let cycle_names: Vec<&str> = scc
696                    .iter()
697                    .map(|&idx| flat_tasks[idx].name.as_str())
698                    .collect();
699
700                return Err(IntentError::InvalidInput(format!(
701                    "Circular dependency detected: {}",
702                    cycle_names.join(" → ")
703                )));
704            }
705        }
706
707        Ok(())
708    }
709
710    /// Tarjan's algorithm for finding strongly connected components
711    /// Returns a list of SCCs, where each SCC is a list of node indices
712    fn tarjan_scc(&self, graph: &[Vec<usize>]) -> Vec<Vec<usize>> {
713        let n = graph.len();
714        let mut index = 0;
715        let mut stack = Vec::new();
716        let mut indices = vec![None; n];
717        let mut lowlinks = vec![0; n];
718        let mut on_stack = vec![false; n];
719        let mut sccs = Vec::new();
720
721        #[allow(clippy::too_many_arguments)]
722        fn strongconnect(
723            v: usize,
724            graph: &[Vec<usize>],
725            index: &mut usize,
726            stack: &mut Vec<usize>,
727            indices: &mut [Option<usize>],
728            lowlinks: &mut [usize],
729            on_stack: &mut [bool],
730            sccs: &mut Vec<Vec<usize>>,
731        ) {
732            // Set the depth index for v to the smallest unused index
733            indices[v] = Some(*index);
734            lowlinks[v] = *index;
735            *index += 1;
736            stack.push(v);
737            on_stack[v] = true;
738
739            // Consider successors of v
740            for &w in &graph[v] {
741                if indices[w].is_none() {
742                    // Successor w has not yet been visited; recurse on it
743                    strongconnect(w, graph, index, stack, indices, lowlinks, on_stack, sccs);
744                    lowlinks[v] = lowlinks[v].min(lowlinks[w]);
745                } else if on_stack[w] {
746                    // Successor w is in stack and hence in the current SCC
747                    lowlinks[v] = lowlinks[v].min(indices[w].unwrap());
748                }
749            }
750
751            // If v is a root node, pop the stack and generate an SCC
752            if lowlinks[v] == indices[v].unwrap() {
753                let mut scc = Vec::new();
754                loop {
755                    let w = stack.pop().unwrap();
756                    on_stack[w] = false;
757                    scc.push(w);
758                    if w == v {
759                        break;
760                    }
761                }
762                sccs.push(scc);
763            }
764        }
765
766        // Find SCCs for all nodes
767        for v in 0..n {
768            if indices[v].is_none() {
769                strongconnect(
770                    v,
771                    graph,
772                    &mut index,
773                    &mut stack,
774                    &mut indices,
775                    &mut lowlinks,
776                    &mut on_stack,
777                    &mut sccs,
778                );
779            }
780        }
781
782        sccs
783    }
784}
785
786#[cfg(test)]
787mod tests {
788    use super::*;
789
790    #[test]
791    fn test_priority_value_to_int() {
792        assert_eq!(PriorityValue::Critical.to_int(), 1);
793        assert_eq!(PriorityValue::High.to_int(), 2);
794        assert_eq!(PriorityValue::Medium.to_int(), 3);
795        assert_eq!(PriorityValue::Low.to_int(), 4);
796    }
797
798    #[test]
799    fn test_priority_value_from_int() {
800        assert_eq!(PriorityValue::from_int(1), Some(PriorityValue::Critical));
801        assert_eq!(PriorityValue::from_int(2), Some(PriorityValue::High));
802        assert_eq!(PriorityValue::from_int(3), Some(PriorityValue::Medium));
803        assert_eq!(PriorityValue::from_int(4), Some(PriorityValue::Low));
804        assert_eq!(PriorityValue::from_int(999), None);
805    }
806
807    #[test]
808    fn test_priority_value_as_str() {
809        assert_eq!(PriorityValue::Critical.as_str(), "critical");
810        assert_eq!(PriorityValue::High.as_str(), "high");
811        assert_eq!(PriorityValue::Medium.as_str(), "medium");
812        assert_eq!(PriorityValue::Low.as_str(), "low");
813    }
814
815    #[test]
816    fn test_plan_request_deserialization_minimal() {
817        let json = r#"{"tasks": [{"name": "Test Task"}]}"#;
818        let request: PlanRequest = serde_json::from_str(json).unwrap();
819
820        assert_eq!(request.tasks.len(), 1);
821        assert_eq!(request.tasks[0].name, "Test Task");
822        assert_eq!(request.tasks[0].spec, None);
823        assert_eq!(request.tasks[0].priority, None);
824        assert_eq!(request.tasks[0].children, None);
825        assert_eq!(request.tasks[0].depends_on, None);
826        assert_eq!(request.tasks[0].task_id, None);
827    }
828
829    #[test]
830    fn test_plan_request_deserialization_full() {
831        let json = r#"{
832            "tasks": [{
833                "name": "Parent Task",
834                "spec": "Parent spec",
835                "priority": "high",
836                "children": [{
837                    "name": "Child Task",
838                    "spec": "Child spec"
839                }],
840                "depends_on": ["Other Task"],
841                "task_id": 42
842            }]
843        }"#;
844
845        let request: PlanRequest = serde_json::from_str(json).unwrap();
846
847        assert_eq!(request.tasks.len(), 1);
848        let parent = &request.tasks[0];
849        assert_eq!(parent.name, "Parent Task");
850        assert_eq!(parent.spec, Some("Parent spec".to_string()));
851        assert_eq!(parent.priority, Some(PriorityValue::High));
852        assert_eq!(parent.task_id, Some(42));
853
854        let children = parent.children.as_ref().unwrap();
855        assert_eq!(children.len(), 1);
856        assert_eq!(children[0].name, "Child Task");
857
858        let depends = parent.depends_on.as_ref().unwrap();
859        assert_eq!(depends.len(), 1);
860        assert_eq!(depends[0], "Other Task");
861    }
862
863    #[test]
864    fn test_plan_request_serialization() {
865        let request = PlanRequest {
866            tasks: vec![TaskTree {
867                name: "Test Task".to_string(),
868                spec: Some("Test spec".to_string()),
869                priority: Some(PriorityValue::Medium),
870                children: None,
871                depends_on: None,
872                task_id: None,
873                status: None,
874                active_form: None,
875            }],
876        };
877
878        let json = serde_json::to_string(&request).unwrap();
879        assert!(json.contains("\"name\":\"Test Task\""));
880        assert!(json.contains("\"spec\":\"Test spec\""));
881        assert!(json.contains("\"priority\":\"medium\""));
882    }
883
884    #[test]
885    fn test_plan_result_success() {
886        let mut map = HashMap::new();
887        map.insert("Task 1".to_string(), 1);
888        map.insert("Task 2".to_string(), 2);
889
890        let result = PlanResult::success(map.clone(), 2, 0, 1);
891
892        assert!(result.success);
893        assert_eq!(result.task_id_map, map);
894        assert_eq!(result.created_count, 2);
895        assert_eq!(result.updated_count, 0);
896        assert_eq!(result.dependency_count, 1);
897        assert_eq!(result.error, None);
898    }
899
900    #[test]
901    fn test_plan_result_error() {
902        let result = PlanResult::error("Test error");
903
904        assert!(!result.success);
905        assert_eq!(result.task_id_map.len(), 0);
906        assert_eq!(result.created_count, 0);
907        assert_eq!(result.updated_count, 0);
908        assert_eq!(result.dependency_count, 0);
909        assert_eq!(result.error, Some("Test error".to_string()));
910    }
911
912    #[test]
913    fn test_task_tree_nested() {
914        let tree = TaskTree {
915            name: "Parent".to_string(),
916            spec: None,
917            priority: None,
918            children: Some(vec![
919                TaskTree {
920                    name: "Child 1".to_string(),
921                    spec: None,
922                    priority: None,
923                    children: None,
924                    depends_on: None,
925                    task_id: None,
926                    status: None,
927                    active_form: None,
928                },
929                TaskTree {
930                    name: "Child 2".to_string(),
931                    spec: None,
932                    priority: Some(PriorityValue::High),
933                    children: None,
934                    depends_on: None,
935                    task_id: None,
936                    status: None,
937                    active_form: None,
938                },
939            ]),
940            depends_on: None,
941            task_id: None,
942            status: None,
943            active_form: None,
944        };
945
946        let json = serde_json::to_string_pretty(&tree).unwrap();
947        let deserialized: TaskTree = serde_json::from_str(&json).unwrap();
948
949        assert_eq!(tree, deserialized);
950        assert_eq!(deserialized.children.as_ref().unwrap().len(), 2);
951    }
952
953    #[test]
954    fn test_priority_value_case_insensitive_deserialization() {
955        // Test lowercase
956        let json = r#"{"name": "Test", "priority": "high"}"#;
957        let task: TaskTree = serde_json::from_str(json).unwrap();
958        assert_eq!(task.priority, Some(PriorityValue::High));
959
960        // Serde expects exact case match for rename_all = "lowercase"
961        // So "High" would fail, which is correct behavior
962    }
963
964    #[test]
965    fn test_extract_all_names_simple() {
966        let tasks = vec![
967            TaskTree {
968                name: "Task 1".to_string(),
969                spec: None,
970                priority: None,
971                children: None,
972                depends_on: None,
973                task_id: None,
974                status: None,
975                active_form: None,
976            },
977            TaskTree {
978                name: "Task 2".to_string(),
979                spec: None,
980                priority: None,
981                children: None,
982                depends_on: None,
983                task_id: None,
984                status: None,
985                active_form: None,
986            },
987        ];
988
989        let names = extract_all_names(&tasks);
990        assert_eq!(names, vec!["Task 1", "Task 2"]);
991    }
992
993    #[test]
994    fn test_extract_all_names_nested() {
995        let tasks = vec![TaskTree {
996            name: "Parent".to_string(),
997            spec: None,
998            priority: None,
999            children: Some(vec![
1000                TaskTree {
1001                    name: "Child 1".to_string(),
1002                    spec: None,
1003                    priority: None,
1004                    children: None,
1005                    depends_on: None,
1006                    task_id: None,
1007                    status: None,
1008                    active_form: None,
1009                },
1010                TaskTree {
1011                    name: "Child 2".to_string(),
1012                    spec: None,
1013                    priority: None,
1014                    children: Some(vec![TaskTree {
1015                        name: "Grandchild".to_string(),
1016                        spec: None,
1017                        priority: None,
1018                        children: None,
1019                        depends_on: None,
1020                        task_id: None,
1021                        status: None,
1022                        active_form: None,
1023                    }]),
1024                    depends_on: None,
1025                    task_id: None,
1026                    status: None,
1027                    active_form: None,
1028                },
1029            ]),
1030            depends_on: None,
1031            task_id: None,
1032            status: None,
1033            active_form: None,
1034        }];
1035
1036        let names = extract_all_names(&tasks);
1037        assert_eq!(names, vec!["Parent", "Child 1", "Child 2", "Grandchild"]);
1038    }
1039
1040    #[test]
1041    fn test_flatten_task_tree_simple() {
1042        let tasks = vec![TaskTree {
1043            name: "Task 1".to_string(),
1044            spec: Some("Spec 1".to_string()),
1045            priority: Some(PriorityValue::High),
1046            children: None,
1047            depends_on: Some(vec!["Task 0".to_string()]),
1048            task_id: None,
1049            status: None,
1050            active_form: None,
1051        }];
1052
1053        let flat = flatten_task_tree(&tasks);
1054        assert_eq!(flat.len(), 1);
1055        assert_eq!(flat[0].name, "Task 1");
1056        assert_eq!(flat[0].spec, Some("Spec 1".to_string()));
1057        assert_eq!(flat[0].priority, Some(PriorityValue::High));
1058        assert_eq!(flat[0].parent_name, None);
1059        assert_eq!(flat[0].depends_on, vec!["Task 0"]);
1060    }
1061
1062    #[test]
1063    fn test_flatten_task_tree_nested() {
1064        let tasks = vec![TaskTree {
1065            name: "Parent".to_string(),
1066            spec: None,
1067            priority: None,
1068            children: Some(vec![
1069                TaskTree {
1070                    name: "Child 1".to_string(),
1071                    spec: None,
1072                    priority: None,
1073                    children: None,
1074                    depends_on: None,
1075                    task_id: None,
1076                    status: None,
1077                    active_form: None,
1078                },
1079                TaskTree {
1080                    name: "Child 2".to_string(),
1081                    spec: None,
1082                    priority: None,
1083                    children: None,
1084                    depends_on: None,
1085                    task_id: None,
1086                    status: None,
1087                    active_form: None,
1088                },
1089            ]),
1090            depends_on: None,
1091            task_id: None,
1092            status: None,
1093            active_form: None,
1094        }];
1095
1096        let flat = flatten_task_tree(&tasks);
1097        assert_eq!(flat.len(), 3);
1098
1099        // Parent should have no parent_name
1100        assert_eq!(flat[0].name, "Parent");
1101        assert_eq!(flat[0].parent_name, None);
1102
1103        // Children should have Parent as parent_name
1104        assert_eq!(flat[1].name, "Child 1");
1105        assert_eq!(flat[1].parent_name, Some("Parent".to_string()));
1106
1107        assert_eq!(flat[2].name, "Child 2");
1108        assert_eq!(flat[2].parent_name, Some("Parent".to_string()));
1109    }
1110
1111    #[test]
1112    fn test_classify_operations_all_create() {
1113        let flat_tasks = vec![
1114            FlatTask {
1115                name: "Task 1".to_string(),
1116                spec: None,
1117                priority: None,
1118                parent_name: None,
1119                depends_on: vec![],
1120                task_id: None,
1121                status: None,
1122                active_form: None,
1123            },
1124            FlatTask {
1125                name: "Task 2".to_string(),
1126                spec: None,
1127                priority: None,
1128                parent_name: None,
1129                depends_on: vec![],
1130                task_id: None,
1131                status: None,
1132                active_form: None,
1133            },
1134        ];
1135
1136        let existing = HashMap::new();
1137        let operations = classify_operations(&flat_tasks, &existing);
1138
1139        assert_eq!(operations.len(), 2);
1140        assert!(matches!(operations[0], Operation::Create(_)));
1141        assert!(matches!(operations[1], Operation::Create(_)));
1142    }
1143
1144    #[test]
1145    fn test_classify_operations_all_update() {
1146        let flat_tasks = vec![
1147            FlatTask {
1148                name: "Task 1".to_string(),
1149                spec: None,
1150                priority: None,
1151                parent_name: None,
1152                depends_on: vec![],
1153                task_id: None,
1154                status: None,
1155                active_form: None,
1156            },
1157            FlatTask {
1158                name: "Task 2".to_string(),
1159                spec: None,
1160                priority: None,
1161                parent_name: None,
1162                depends_on: vec![],
1163                task_id: None,
1164                status: None,
1165                active_form: None,
1166            },
1167        ];
1168
1169        let mut existing = HashMap::new();
1170        existing.insert("Task 1".to_string(), 1);
1171        existing.insert("Task 2".to_string(), 2);
1172
1173        let operations = classify_operations(&flat_tasks, &existing);
1174
1175        assert_eq!(operations.len(), 2);
1176        assert!(matches!(
1177            operations[0],
1178            Operation::Update { task_id: 1, .. }
1179        ));
1180        assert!(matches!(
1181            operations[1],
1182            Operation::Update { task_id: 2, .. }
1183        ));
1184    }
1185
1186    #[test]
1187    fn test_classify_operations_mixed() {
1188        let flat_tasks = vec![
1189            FlatTask {
1190                name: "Existing Task".to_string(),
1191                spec: None,
1192                priority: None,
1193                parent_name: None,
1194                depends_on: vec![],
1195                task_id: None,
1196                status: None,
1197                active_form: None,
1198            },
1199            FlatTask {
1200                name: "New Task".to_string(),
1201                spec: None,
1202                priority: None,
1203                parent_name: None,
1204                depends_on: vec![],
1205                task_id: None,
1206                status: None,
1207                active_form: None,
1208            },
1209        ];
1210
1211        let mut existing = HashMap::new();
1212        existing.insert("Existing Task".to_string(), 42);
1213
1214        let operations = classify_operations(&flat_tasks, &existing);
1215
1216        assert_eq!(operations.len(), 2);
1217        assert!(matches!(
1218            operations[0],
1219            Operation::Update { task_id: 42, .. }
1220        ));
1221        assert!(matches!(operations[1], Operation::Create(_)));
1222    }
1223
1224    #[test]
1225    fn test_classify_operations_explicit_task_id() {
1226        let flat_tasks = vec![FlatTask {
1227            name: "Task".to_string(),
1228            spec: None,
1229            priority: None,
1230            parent_name: None,
1231            depends_on: vec![],
1232            task_id: Some(99), // Explicit task_id
1233            status: None,
1234            active_form: None,
1235        }];
1236
1237        let existing = HashMap::new(); // Not in existing
1238
1239        let operations = classify_operations(&flat_tasks, &existing);
1240
1241        // Should still be update because of explicit task_id
1242        assert_eq!(operations.len(), 1);
1243        assert!(matches!(
1244            operations[0],
1245            Operation::Update { task_id: 99, .. }
1246        ));
1247    }
1248
1249    #[test]
1250    fn test_find_duplicate_names_no_duplicates() {
1251        let tasks = vec![
1252            TaskTree {
1253                name: "Task 1".to_string(),
1254                spec: None,
1255                priority: None,
1256                children: None,
1257                depends_on: None,
1258                task_id: None,
1259                status: None,
1260                active_form: None,
1261            },
1262            TaskTree {
1263                name: "Task 2".to_string(),
1264                spec: None,
1265                priority: None,
1266                children: None,
1267                depends_on: None,
1268                task_id: None,
1269                status: None,
1270                active_form: None,
1271            },
1272        ];
1273
1274        let duplicates = find_duplicate_names(&tasks);
1275        assert_eq!(duplicates.len(), 0);
1276    }
1277
1278    #[test]
1279    fn test_find_duplicate_names_with_duplicates() {
1280        let tasks = vec![
1281            TaskTree {
1282                name: "Duplicate".to_string(),
1283                spec: None,
1284                priority: None,
1285                children: None,
1286                depends_on: None,
1287                task_id: None,
1288                status: None,
1289                active_form: None,
1290            },
1291            TaskTree {
1292                name: "Unique".to_string(),
1293                spec: None,
1294                priority: None,
1295                children: None,
1296                depends_on: None,
1297                task_id: None,
1298                status: None,
1299                active_form: None,
1300            },
1301            TaskTree {
1302                name: "Duplicate".to_string(),
1303                spec: None,
1304                priority: None,
1305                children: None,
1306                depends_on: None,
1307                task_id: None,
1308                status: None,
1309                active_form: None,
1310            },
1311        ];
1312
1313        let duplicates = find_duplicate_names(&tasks);
1314        assert_eq!(duplicates.len(), 1);
1315        assert_eq!(duplicates[0], "Duplicate");
1316    }
1317
1318    #[test]
1319    fn test_find_duplicate_names_nested() {
1320        let tasks = vec![TaskTree {
1321            name: "Parent".to_string(),
1322            spec: None,
1323            priority: None,
1324            children: Some(vec![TaskTree {
1325                name: "Parent".to_string(), // Duplicate name in child
1326                spec: None,
1327                priority: None,
1328                children: None,
1329                depends_on: None,
1330                task_id: None,
1331                status: None,
1332                active_form: None,
1333            }]),
1334            depends_on: None,
1335            task_id: None,
1336            status: None,
1337            active_form: None,
1338        }];
1339
1340        let duplicates = find_duplicate_names(&tasks);
1341        assert_eq!(duplicates.len(), 1);
1342        assert_eq!(duplicates[0], "Parent");
1343    }
1344
1345    #[test]
1346    fn test_flatten_task_tree_empty() {
1347        let tasks: Vec<TaskTree> = vec![];
1348        let flat = flatten_task_tree(&tasks);
1349        assert_eq!(flat.len(), 0);
1350    }
1351
1352    #[test]
1353    fn test_flatten_task_tree_deep_nesting() {
1354        // Create 4-level deep nesting: Root -> L1 -> L2 -> L3
1355        let tasks = vec![TaskTree {
1356            name: "Root".to_string(),
1357            spec: None,
1358            priority: None,
1359            children: Some(vec![TaskTree {
1360                name: "Level1".to_string(),
1361                spec: None,
1362                priority: None,
1363                children: Some(vec![TaskTree {
1364                    name: "Level2".to_string(),
1365                    spec: None,
1366                    priority: None,
1367                    children: Some(vec![TaskTree {
1368                        name: "Level3".to_string(),
1369                        spec: None,
1370                        priority: None,
1371                        children: None,
1372                        depends_on: None,
1373                        task_id: None,
1374                        status: None,
1375                        active_form: None,
1376                    }]),
1377                    depends_on: None,
1378                    task_id: None,
1379                    status: None,
1380                    active_form: None,
1381                }]),
1382                depends_on: None,
1383                task_id: None,
1384                status: None,
1385                active_form: None,
1386            }]),
1387            depends_on: None,
1388            task_id: None,
1389            status: None,
1390            active_form: None,
1391        }];
1392
1393        let flat = flatten_task_tree(&tasks);
1394        assert_eq!(flat.len(), 4);
1395
1396        // Check parent relationships
1397        assert_eq!(flat[0].name, "Root");
1398        assert_eq!(flat[0].parent_name, None);
1399
1400        assert_eq!(flat[1].name, "Level1");
1401        assert_eq!(flat[1].parent_name, Some("Root".to_string()));
1402
1403        assert_eq!(flat[2].name, "Level2");
1404        assert_eq!(flat[2].parent_name, Some("Level1".to_string()));
1405
1406        assert_eq!(flat[3].name, "Level3");
1407        assert_eq!(flat[3].parent_name, Some("Level2".to_string()));
1408    }
1409
1410    #[test]
1411    fn test_flatten_task_tree_many_siblings() {
1412        let children: Vec<TaskTree> = (0..10)
1413            .map(|i| TaskTree {
1414                name: format!("Child {}", i),
1415                spec: None,
1416                priority: None,
1417                children: None,
1418                depends_on: None,
1419                task_id: None,
1420                status: None,
1421                active_form: None,
1422            })
1423            .collect();
1424
1425        let tasks = vec![TaskTree {
1426            name: "Parent".to_string(),
1427            spec: None,
1428            priority: None,
1429            children: Some(children),
1430            depends_on: None,
1431            task_id: None,
1432            status: None,
1433            active_form: None,
1434        }];
1435
1436        let flat = flatten_task_tree(&tasks);
1437        assert_eq!(flat.len(), 11); // 1 parent + 10 children
1438
1439        // All children should have same parent
1440        for child in flat.iter().skip(1).take(10) {
1441            assert_eq!(child.parent_name, Some("Parent".to_string()));
1442        }
1443    }
1444
1445    #[test]
1446    fn test_flatten_task_tree_complex_mixed() {
1447        // Complex structure with multiple levels and siblings
1448        let tasks = vec![
1449            TaskTree {
1450                name: "Task 1".to_string(),
1451                spec: None,
1452                priority: None,
1453                children: Some(vec![
1454                    TaskTree {
1455                        name: "Task 1.1".to_string(),
1456                        spec: None,
1457                        priority: None,
1458                        children: None,
1459                        depends_on: None,
1460                        task_id: None,
1461                        status: None,
1462                        active_form: None,
1463                    },
1464                    TaskTree {
1465                        name: "Task 1.2".to_string(),
1466                        spec: None,
1467                        priority: None,
1468                        children: Some(vec![TaskTree {
1469                            name: "Task 1.2.1".to_string(),
1470                            spec: None,
1471                            priority: None,
1472                            children: None,
1473                            depends_on: None,
1474                            task_id: None,
1475                            status: None,
1476                            active_form: None,
1477                        }]),
1478                        depends_on: None,
1479                        task_id: None,
1480                        status: None,
1481                        active_form: None,
1482                    },
1483                ]),
1484                depends_on: None,
1485                task_id: None,
1486                status: None,
1487                active_form: None,
1488            },
1489            TaskTree {
1490                name: "Task 2".to_string(),
1491                spec: None,
1492                priority: None,
1493                children: None,
1494                depends_on: Some(vec!["Task 1".to_string()]),
1495                task_id: None,
1496                status: None,
1497                active_form: None,
1498            },
1499        ];
1500
1501        let flat = flatten_task_tree(&tasks);
1502        assert_eq!(flat.len(), 5);
1503
1504        // Verify structure
1505        assert_eq!(flat[0].name, "Task 1");
1506        assert_eq!(flat[0].parent_name, None);
1507
1508        assert_eq!(flat[1].name, "Task 1.1");
1509        assert_eq!(flat[1].parent_name, Some("Task 1".to_string()));
1510
1511        assert_eq!(flat[2].name, "Task 1.2");
1512        assert_eq!(flat[2].parent_name, Some("Task 1".to_string()));
1513
1514        assert_eq!(flat[3].name, "Task 1.2.1");
1515        assert_eq!(flat[3].parent_name, Some("Task 1.2".to_string()));
1516
1517        assert_eq!(flat[4].name, "Task 2");
1518        assert_eq!(flat[4].parent_name, None);
1519        assert_eq!(flat[4].depends_on, vec!["Task 1"]);
1520    }
1521
1522    #[tokio::test]
1523    async fn test_plan_executor_integration() {
1524        use crate::test_utils::test_helpers::TestContext;
1525
1526        let ctx = TestContext::new().await;
1527
1528        // Create a plan with hierarchy and dependencies
1529        let request = PlanRequest {
1530            tasks: vec![TaskTree {
1531                name: "Integration Test Plan".to_string(),
1532                spec: Some("Test plan execution end-to-end".to_string()),
1533                priority: Some(PriorityValue::High),
1534                children: Some(vec![
1535                    TaskTree {
1536                        name: "Subtask A".to_string(),
1537                        spec: Some("First subtask".to_string()),
1538                        priority: None,
1539                        children: None,
1540                        depends_on: None,
1541                        task_id: None,
1542                        status: None,
1543                        active_form: None,
1544                    },
1545                    TaskTree {
1546                        name: "Subtask B".to_string(),
1547                        spec: Some("Second subtask depends on A".to_string()),
1548                        priority: None,
1549                        children: None,
1550                        depends_on: Some(vec!["Subtask A".to_string()]),
1551                        task_id: None,
1552                        status: None,
1553                        active_form: None,
1554                    },
1555                ]),
1556                depends_on: None,
1557                task_id: None,
1558                status: None,
1559                active_form: None,
1560            }],
1561        };
1562
1563        // Execute the plan
1564        let executor = PlanExecutor::new(&ctx.pool);
1565        let result = executor.execute(&request).await.unwrap();
1566
1567        // Verify success
1568        assert!(result.success, "Plan execution should succeed");
1569        assert_eq!(result.created_count, 3, "Should create 3 tasks");
1570        assert_eq!(result.updated_count, 0, "Should not update any tasks");
1571        assert_eq!(result.dependency_count, 1, "Should create 1 dependency");
1572        assert!(result.error.is_none(), "Should have no error");
1573
1574        // Verify task ID map
1575        assert_eq!(result.task_id_map.len(), 3);
1576        assert!(result.task_id_map.contains_key("Integration Test Plan"));
1577        assert!(result.task_id_map.contains_key("Subtask A"));
1578        assert!(result.task_id_map.contains_key("Subtask B"));
1579
1580        // Verify tasks were created in database
1581        let parent_id = *result.task_id_map.get("Integration Test Plan").unwrap();
1582        let subtask_a_id = *result.task_id_map.get("Subtask A").unwrap();
1583        let subtask_b_id = *result.task_id_map.get("Subtask B").unwrap();
1584
1585        // Check parent task
1586        let parent: (String, String, i64, Option<i64>) =
1587            sqlx::query_as("SELECT name, spec, priority, parent_id FROM tasks WHERE id = ?")
1588                .bind(parent_id)
1589                .fetch_one(&ctx.pool)
1590                .await
1591                .unwrap();
1592
1593        assert_eq!(parent.0, "Integration Test Plan");
1594        assert_eq!(parent.1, "Test plan execution end-to-end");
1595        assert_eq!(parent.2, 2); // High priority = 2
1596        assert_eq!(parent.3, None); // No parent
1597
1598        // Check subtask A
1599        let subtask_a: (String, Option<i64>) =
1600            sqlx::query_as(crate::sql_constants::SELECT_TASK_NAME_PARENT)
1601                .bind(subtask_a_id)
1602                .fetch_one(&ctx.pool)
1603                .await
1604                .unwrap();
1605
1606        assert_eq!(subtask_a.0, "Subtask A");
1607        assert_eq!(subtask_a.1, Some(parent_id)); // Parent should be set
1608
1609        // Check dependency
1610        let dep: (i64, i64) = sqlx::query_as(
1611            "SELECT blocking_task_id, blocked_task_id FROM dependencies WHERE blocked_task_id = ?",
1612        )
1613        .bind(subtask_b_id)
1614        .fetch_one(&ctx.pool)
1615        .await
1616        .unwrap();
1617
1618        assert_eq!(dep.0, subtask_a_id); // Blocking task
1619        assert_eq!(dep.1, subtask_b_id); // Blocked task
1620    }
1621
1622    #[tokio::test]
1623    async fn test_plan_executor_idempotency() {
1624        use crate::test_utils::test_helpers::TestContext;
1625
1626        let ctx = TestContext::new().await;
1627
1628        // Create a plan
1629        let request = PlanRequest {
1630            tasks: vec![TaskTree {
1631                name: "Idempotent Task".to_string(),
1632                spec: Some("Initial spec".to_string()),
1633                priority: Some(PriorityValue::High),
1634                children: Some(vec![
1635                    TaskTree {
1636                        name: "Child 1".to_string(),
1637                        spec: Some("Child spec 1".to_string()),
1638                        priority: None,
1639                        children: None,
1640                        depends_on: None,
1641                        task_id: None,
1642                        status: None,
1643                        active_form: None,
1644                    },
1645                    TaskTree {
1646                        name: "Child 2".to_string(),
1647                        spec: Some("Child spec 2".to_string()),
1648                        priority: Some(PriorityValue::Low),
1649                        children: None,
1650                        depends_on: None,
1651                        task_id: None,
1652                        status: None,
1653                        active_form: None,
1654                    },
1655                ]),
1656                depends_on: None,
1657                task_id: None,
1658                status: None,
1659                active_form: None,
1660            }],
1661        };
1662
1663        let executor = PlanExecutor::new(&ctx.pool);
1664
1665        // First execution - should create all tasks
1666        let result1 = executor.execute(&request).await.unwrap();
1667        assert!(result1.success, "First execution should succeed");
1668        assert_eq!(result1.created_count, 3, "Should create 3 tasks");
1669        assert_eq!(result1.updated_count, 0, "Should not update any tasks");
1670        assert_eq!(result1.task_id_map.len(), 3, "Should have 3 task IDs");
1671
1672        // Get task IDs from first execution
1673        let parent_id_1 = *result1.task_id_map.get("Idempotent Task").unwrap();
1674        let child1_id_1 = *result1.task_id_map.get("Child 1").unwrap();
1675        let child2_id_1 = *result1.task_id_map.get("Child 2").unwrap();
1676
1677        // Second execution with same plan - should update all tasks (idempotent)
1678        let result2 = executor.execute(&request).await.unwrap();
1679        assert!(result2.success, "Second execution should succeed");
1680        assert_eq!(result2.created_count, 0, "Should not create any new tasks");
1681        assert_eq!(result2.updated_count, 3, "Should update all 3 tasks");
1682        assert_eq!(result2.task_id_map.len(), 3, "Should still have 3 task IDs");
1683
1684        // Task IDs should remain the same (idempotent)
1685        let parent_id_2 = *result2.task_id_map.get("Idempotent Task").unwrap();
1686        let child1_id_2 = *result2.task_id_map.get("Child 1").unwrap();
1687        let child2_id_2 = *result2.task_id_map.get("Child 2").unwrap();
1688
1689        assert_eq!(parent_id_1, parent_id_2, "Parent ID should not change");
1690        assert_eq!(child1_id_1, child1_id_2, "Child 1 ID should not change");
1691        assert_eq!(child2_id_1, child2_id_2, "Child 2 ID should not change");
1692
1693        // Verify data in database hasn't changed (spec, priority)
1694        let parent: (String, i64) = sqlx::query_as("SELECT spec, priority FROM tasks WHERE id = ?")
1695            .bind(parent_id_2)
1696            .fetch_one(&ctx.pool)
1697            .await
1698            .unwrap();
1699
1700        assert_eq!(parent.0, "Initial spec");
1701        assert_eq!(parent.1, 2); // High priority = 2
1702
1703        // Third execution with modified plan - should update with new values
1704        let modified_request = PlanRequest {
1705            tasks: vec![TaskTree {
1706                name: "Idempotent Task".to_string(),
1707                spec: Some("Updated spec".to_string()), // Changed
1708                priority: Some(PriorityValue::Critical), // Changed
1709                children: Some(vec![
1710                    TaskTree {
1711                        name: "Child 1".to_string(),
1712                        spec: Some("Updated child spec 1".to_string()), // Changed
1713                        priority: None,
1714                        children: None,
1715                        depends_on: None,
1716                        task_id: None,
1717                        status: None,
1718                        active_form: None,
1719                    },
1720                    TaskTree {
1721                        name: "Child 2".to_string(),
1722                        spec: Some("Child spec 2".to_string()), // Unchanged
1723                        priority: Some(PriorityValue::Low),
1724                        children: None,
1725                        depends_on: None,
1726                        task_id: None,
1727                        status: None,
1728                        active_form: None,
1729                    },
1730                ]),
1731                depends_on: None,
1732                task_id: None,
1733                status: None,
1734                active_form: None,
1735            }],
1736        };
1737
1738        let result3 = executor.execute(&modified_request).await.unwrap();
1739        assert!(result3.success, "Third execution should succeed");
1740        assert_eq!(result3.created_count, 0, "Should not create any new tasks");
1741        assert_eq!(result3.updated_count, 3, "Should update all 3 tasks");
1742
1743        // Verify updates were applied
1744        let updated_parent: (String, i64) =
1745            sqlx::query_as("SELECT spec, priority FROM tasks WHERE id = ?")
1746                .bind(parent_id_2)
1747                .fetch_one(&ctx.pool)
1748                .await
1749                .unwrap();
1750
1751        assert_eq!(updated_parent.0, "Updated spec");
1752        assert_eq!(updated_parent.1, 1); // Critical priority = 1
1753
1754        let updated_child1: (String,) = sqlx::query_as("SELECT spec FROM tasks WHERE id = ?")
1755            .bind(child1_id_2)
1756            .fetch_one(&ctx.pool)
1757            .await
1758            .unwrap();
1759
1760        assert_eq!(updated_child1.0, "Updated child spec 1");
1761    }
1762
1763    #[tokio::test]
1764    async fn test_plan_executor_dependencies() {
1765        use crate::test_utils::test_helpers::TestContext;
1766
1767        let ctx = TestContext::new().await;
1768
1769        // Create a plan with multiple dependency relationships
1770        let request = PlanRequest {
1771            tasks: vec![
1772                TaskTree {
1773                    name: "Foundation".to_string(),
1774                    spec: Some("Base layer".to_string()),
1775                    priority: Some(PriorityValue::Critical),
1776                    children: None,
1777                    depends_on: None,
1778                    task_id: None,
1779                    status: None,
1780                    active_form: None,
1781                },
1782                TaskTree {
1783                    name: "Layer 1".to_string(),
1784                    spec: Some("Depends on Foundation".to_string()),
1785                    priority: Some(PriorityValue::High),
1786                    children: None,
1787                    depends_on: Some(vec!["Foundation".to_string()]),
1788                    task_id: None,
1789                    status: None,
1790                    active_form: None,
1791                },
1792                TaskTree {
1793                    name: "Layer 2".to_string(),
1794                    spec: Some("Depends on Layer 1".to_string()),
1795                    priority: None,
1796                    children: None,
1797                    depends_on: Some(vec!["Layer 1".to_string()]),
1798                    task_id: None,
1799                    status: None,
1800                    active_form: None,
1801                },
1802                TaskTree {
1803                    name: "Integration".to_string(),
1804                    spec: Some("Depends on both Foundation and Layer 2".to_string()),
1805                    priority: None,
1806                    children: None,
1807                    depends_on: Some(vec!["Foundation".to_string(), "Layer 2".to_string()]),
1808                    task_id: None,
1809                    status: None,
1810                    active_form: None,
1811                },
1812            ],
1813        };
1814
1815        let executor = PlanExecutor::new(&ctx.pool);
1816        let result = executor.execute(&request).await.unwrap();
1817
1818        assert!(result.success, "Plan execution should succeed");
1819        assert_eq!(result.created_count, 4, "Should create 4 tasks");
1820        assert_eq!(result.dependency_count, 4, "Should create 4 dependencies");
1821
1822        // Get task IDs
1823        let foundation_id = *result.task_id_map.get("Foundation").unwrap();
1824        let layer1_id = *result.task_id_map.get("Layer 1").unwrap();
1825        let layer2_id = *result.task_id_map.get("Layer 2").unwrap();
1826        let integration_id = *result.task_id_map.get("Integration").unwrap();
1827
1828        // Verify dependency: Layer 1 -> Foundation
1829        let deps1: Vec<(i64,)> =
1830            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ?")
1831                .bind(layer1_id)
1832                .fetch_all(&ctx.pool)
1833                .await
1834                .unwrap();
1835
1836        assert_eq!(deps1.len(), 1);
1837        assert_eq!(deps1[0].0, foundation_id);
1838
1839        // Verify dependency: Layer 2 -> Layer 1
1840        let deps2: Vec<(i64,)> =
1841            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ?")
1842                .bind(layer2_id)
1843                .fetch_all(&ctx.pool)
1844                .await
1845                .unwrap();
1846
1847        assert_eq!(deps2.len(), 1);
1848        assert_eq!(deps2[0].0, layer1_id);
1849
1850        // Verify dependencies: Integration -> Foundation, Layer 2
1851        let deps3: Vec<(i64,)> =
1852            sqlx::query_as("SELECT blocking_task_id FROM dependencies WHERE blocked_task_id = ? ORDER BY blocking_task_id")
1853                .bind(integration_id)
1854                .fetch_all(&ctx.pool)
1855                .await
1856                .unwrap();
1857
1858        assert_eq!(deps3.len(), 2);
1859        let mut blocking_ids: Vec<i64> = deps3.iter().map(|d| d.0).collect();
1860        blocking_ids.sort();
1861
1862        let mut expected_ids = vec![foundation_id, layer2_id];
1863        expected_ids.sort();
1864
1865        assert_eq!(blocking_ids, expected_ids);
1866    }
1867
1868    #[tokio::test]
1869    async fn test_plan_executor_invalid_dependency() {
1870        use crate::test_utils::test_helpers::TestContext;
1871
1872        let ctx = TestContext::new().await;
1873
1874        // Create a plan with an invalid dependency
1875        let request = PlanRequest {
1876            tasks: vec![TaskTree {
1877                name: "Task A".to_string(),
1878                spec: Some("Depends on non-existent task".to_string()),
1879                priority: None,
1880                children: None,
1881                depends_on: Some(vec!["NonExistent".to_string()]),
1882                task_id: None,
1883                status: None,
1884                active_form: None,
1885            }],
1886        };
1887
1888        let executor = PlanExecutor::new(&ctx.pool);
1889        let result = executor.execute(&request).await.unwrap();
1890
1891        assert!(!result.success, "Plan execution should fail");
1892        assert!(result.error.is_some(), "Should have error message");
1893        let error = result.error.unwrap();
1894        assert!(
1895            error.contains("NonExistent"),
1896            "Error should mention the missing dependency: {}",
1897            error
1898        );
1899    }
1900
1901    #[tokio::test]
1902    async fn test_plan_executor_simple_cycle() {
1903        use crate::test_utils::test_helpers::TestContext;
1904
1905        let ctx = TestContext::new().await;
1906
1907        // Create a plan with a simple cycle: A → B → A
1908        let request = PlanRequest {
1909            tasks: vec![
1910                TaskTree {
1911                    name: "Task A".to_string(),
1912                    spec: Some("Depends on B".to_string()),
1913                    priority: None,
1914                    children: None,
1915                    depends_on: Some(vec!["Task B".to_string()]),
1916                    task_id: None,
1917                    status: None,
1918                    active_form: None,
1919                },
1920                TaskTree {
1921                    name: "Task B".to_string(),
1922                    spec: Some("Depends on A".to_string()),
1923                    priority: None,
1924                    children: None,
1925                    depends_on: Some(vec!["Task A".to_string()]),
1926                    task_id: None,
1927                    status: None,
1928                    active_form: None,
1929                },
1930            ],
1931        };
1932
1933        let executor = PlanExecutor::new(&ctx.pool);
1934        let result = executor.execute(&request).await.unwrap();
1935
1936        assert!(!result.success, "Plan execution should fail");
1937        assert!(result.error.is_some(), "Should have error message");
1938        let error = result.error.unwrap();
1939        assert!(
1940            error.contains("Circular dependency"),
1941            "Error should mention circular dependency: {}",
1942            error
1943        );
1944        assert!(
1945            error.contains("Task A") && error.contains("Task B"),
1946            "Error should mention both tasks in the cycle: {}",
1947            error
1948        );
1949    }
1950
1951    #[tokio::test]
1952    async fn test_plan_executor_complex_cycle() {
1953        use crate::test_utils::test_helpers::TestContext;
1954
1955        let ctx = TestContext::new().await;
1956
1957        // Create a plan with a complex cycle: A → B → C → A
1958        let request = PlanRequest {
1959            tasks: vec![
1960                TaskTree {
1961                    name: "Task A".to_string(),
1962                    spec: Some("Depends on B".to_string()),
1963                    priority: None,
1964                    children: None,
1965                    depends_on: Some(vec!["Task B".to_string()]),
1966                    task_id: None,
1967                    status: None,
1968                    active_form: None,
1969                },
1970                TaskTree {
1971                    name: "Task B".to_string(),
1972                    spec: Some("Depends on C".to_string()),
1973                    priority: None,
1974                    children: None,
1975                    depends_on: Some(vec!["Task C".to_string()]),
1976                    task_id: None,
1977                    status: None,
1978                    active_form: None,
1979                },
1980                TaskTree {
1981                    name: "Task C".to_string(),
1982                    spec: Some("Depends on A".to_string()),
1983                    priority: None,
1984                    children: None,
1985                    depends_on: Some(vec!["Task A".to_string()]),
1986                    task_id: None,
1987                    status: None,
1988                    active_form: None,
1989                },
1990            ],
1991        };
1992
1993        let executor = PlanExecutor::new(&ctx.pool);
1994        let result = executor.execute(&request).await.unwrap();
1995
1996        assert!(!result.success, "Plan execution should fail");
1997        assert!(result.error.is_some(), "Should have error message");
1998        let error = result.error.unwrap();
1999        assert!(
2000            error.contains("Circular dependency"),
2001            "Error should mention circular dependency: {}",
2002            error
2003        );
2004        assert!(
2005            error.contains("Task A") && error.contains("Task B") && error.contains("Task C"),
2006            "Error should mention all tasks in the cycle: {}",
2007            error
2008        );
2009    }
2010
2011    #[tokio::test]
2012    async fn test_plan_executor_valid_dag() {
2013        use crate::test_utils::test_helpers::TestContext;
2014
2015        let ctx = TestContext::new().await;
2016
2017        // Create a valid DAG: no cycles
2018        //   A
2019        //  / \
2020        // B   C
2021        //  \ /
2022        //   D
2023        let request = PlanRequest {
2024            tasks: vec![
2025                TaskTree {
2026                    name: "Task A".to_string(),
2027                    spec: Some("Root task".to_string()),
2028                    priority: None,
2029                    children: None,
2030                    depends_on: None,
2031                    task_id: None,
2032                    status: None,
2033                    active_form: None,
2034                },
2035                TaskTree {
2036                    name: "Task B".to_string(),
2037                    spec: Some("Depends on A".to_string()),
2038                    priority: None,
2039                    children: None,
2040                    depends_on: Some(vec!["Task A".to_string()]),
2041                    task_id: None,
2042                    status: None,
2043                    active_form: None,
2044                },
2045                TaskTree {
2046                    name: "Task C".to_string(),
2047                    spec: Some("Depends on A".to_string()),
2048                    priority: None,
2049                    children: None,
2050                    depends_on: Some(vec!["Task A".to_string()]),
2051                    task_id: None,
2052                    status: None,
2053                    active_form: None,
2054                },
2055                TaskTree {
2056                    name: "Task D".to_string(),
2057                    spec: Some("Depends on B and C".to_string()),
2058                    priority: None,
2059                    children: None,
2060                    depends_on: Some(vec!["Task B".to_string(), "Task C".to_string()]),
2061                    task_id: None,
2062                    status: None,
2063                    active_form: None,
2064                },
2065            ],
2066        };
2067
2068        let executor = PlanExecutor::new(&ctx.pool);
2069        let result = executor.execute(&request).await.unwrap();
2070
2071        assert!(
2072            result.success,
2073            "Plan execution should succeed for valid DAG"
2074        );
2075        assert_eq!(result.created_count, 4, "Should create 4 tasks");
2076        assert_eq!(result.dependency_count, 4, "Should create 4 dependencies");
2077    }
2078
2079    #[tokio::test]
2080    async fn test_plan_executor_self_dependency() {
2081        use crate::test_utils::test_helpers::TestContext;
2082
2083        let ctx = TestContext::new().await;
2084
2085        // Create a plan with self-dependency: A → A
2086        let request = PlanRequest {
2087            tasks: vec![TaskTree {
2088                name: "Task A".to_string(),
2089                spec: Some("Depends on itself".to_string()),
2090                priority: None,
2091                children: None,
2092                depends_on: Some(vec!["Task A".to_string()]),
2093                task_id: None,
2094                status: None,
2095                active_form: None,
2096            }],
2097        };
2098
2099        let executor = PlanExecutor::new(&ctx.pool);
2100        let result = executor.execute(&request).await.unwrap();
2101
2102        assert!(
2103            !result.success,
2104            "Plan execution should fail for self-dependency"
2105        );
2106        assert!(result.error.is_some(), "Should have error message");
2107        let error = result.error.unwrap();
2108        assert!(
2109            error.contains("Circular dependency"),
2110            "Error should mention circular dependency: {}",
2111            error
2112        );
2113    }
2114
2115    // Database query tests
2116    #[tokio::test]
2117    async fn test_find_tasks_by_names_empty() {
2118        use crate::test_utils::test_helpers::TestContext;
2119
2120        let ctx = TestContext::new().await;
2121        let executor = PlanExecutor::new(&ctx.pool);
2122
2123        let result = executor.find_tasks_by_names(&[]).await.unwrap();
2124        assert!(result.is_empty(), "Empty input should return empty map");
2125    }
2126
2127    #[tokio::test]
2128    async fn test_find_tasks_by_names_partial() {
2129        use crate::test_utils::test_helpers::TestContext;
2130
2131        let ctx = TestContext::new().await;
2132        let executor = PlanExecutor::new(&ctx.pool);
2133
2134        // Create some tasks first
2135        let request = PlanRequest {
2136            tasks: vec![
2137                TaskTree {
2138                    name: "Task A".to_string(),
2139                    spec: None,
2140                    priority: None,
2141                    children: None,
2142                    depends_on: None,
2143                    task_id: None,
2144                    status: None,
2145                    active_form: None,
2146                },
2147                TaskTree {
2148                    name: "Task B".to_string(),
2149                    spec: None,
2150                    priority: None,
2151                    children: None,
2152                    depends_on: None,
2153                    task_id: None,
2154                    status: None,
2155                    active_form: None,
2156                },
2157            ],
2158        };
2159        executor.execute(&request).await.unwrap();
2160
2161        // Query for A, B, and C (C doesn't exist)
2162        let names = vec![
2163            "Task A".to_string(),
2164            "Task B".to_string(),
2165            "Task C".to_string(),
2166        ];
2167        let result = executor.find_tasks_by_names(&names).await.unwrap();
2168
2169        assert_eq!(result.len(), 2, "Should find 2 out of 3 tasks");
2170        assert!(result.contains_key("Task A"));
2171        assert!(result.contains_key("Task B"));
2172        assert!(!result.contains_key("Task C"));
2173    }
2174
2175    // Performance tests
2176    #[tokio::test]
2177    async fn test_plan_1000_tasks_performance() {
2178        use crate::test_utils::test_helpers::TestContext;
2179
2180        let ctx = TestContext::new().await;
2181        let executor = PlanExecutor::new(&ctx.pool);
2182
2183        // Generate 1000 flat tasks
2184        let mut tasks = Vec::new();
2185        for i in 0..1000 {
2186            tasks.push(TaskTree {
2187                name: format!("Task {}", i),
2188                spec: Some(format!("Spec for task {}", i)),
2189                priority: Some(PriorityValue::Medium),
2190                children: None,
2191                depends_on: None,
2192                task_id: None,
2193                status: None,
2194                active_form: None,
2195            });
2196        }
2197
2198        let request = PlanRequest { tasks };
2199
2200        let start = std::time::Instant::now();
2201        let result = executor.execute(&request).await.unwrap();
2202        let duration = start.elapsed();
2203
2204        assert!(result.success);
2205        assert_eq!(result.created_count, 1000);
2206        assert!(
2207            duration.as_secs() < 10,
2208            "Should complete 1000 tasks in under 10 seconds, took {:?}",
2209            duration
2210        );
2211
2212        println!("✅ Created 1000 tasks in {:?}", duration);
2213    }
2214
2215    #[tokio::test]
2216    async fn test_plan_deep_nesting_20_levels() {
2217        use crate::test_utils::test_helpers::TestContext;
2218
2219        let ctx = TestContext::new().await;
2220        let executor = PlanExecutor::new(&ctx.pool);
2221
2222        // Generate deep nesting: 20 levels
2223        fn build_deep_tree(depth: usize, current: usize) -> TaskTree {
2224            TaskTree {
2225                name: format!("Level {}", current),
2226                spec: Some(format!("Task at depth {}", current)),
2227                priority: Some(PriorityValue::Low),
2228                children: if current < depth {
2229                    Some(vec![build_deep_tree(depth, current + 1)])
2230                } else {
2231                    None
2232                },
2233                depends_on: None,
2234                task_id: None,
2235                status: None,
2236                active_form: None,
2237            }
2238        }
2239
2240        let request = PlanRequest {
2241            tasks: vec![build_deep_tree(20, 1)],
2242        };
2243
2244        let start = std::time::Instant::now();
2245        let result = executor.execute(&request).await.unwrap();
2246        let duration = start.elapsed();
2247
2248        assert!(result.success);
2249        assert_eq!(
2250            result.created_count, 20,
2251            "Should create 20 tasks (1 per level)"
2252        );
2253        assert!(
2254            duration.as_secs() < 5,
2255            "Should handle 20-level nesting in under 5 seconds, took {:?}",
2256            duration
2257        );
2258
2259        println!("✅ Created 20-level deep tree in {:?}", duration);
2260    }
2261
2262    #[test]
2263    fn test_flatten_preserves_all_fields() {
2264        let tasks = vec![TaskTree {
2265            name: "Full Task".to_string(),
2266            spec: Some("Detailed spec".to_string()),
2267            priority: Some(PriorityValue::Critical),
2268            children: None,
2269            depends_on: Some(vec!["Dep1".to_string(), "Dep2".to_string()]),
2270            task_id: Some(42),
2271            status: None,
2272            active_form: None,
2273        }];
2274
2275        let flat = flatten_task_tree(&tasks);
2276        assert_eq!(flat.len(), 1);
2277
2278        let task = &flat[0];
2279        assert_eq!(task.name, "Full Task");
2280        assert_eq!(task.spec, Some("Detailed spec".to_string()));
2281        assert_eq!(task.priority, Some(PriorityValue::Critical));
2282        assert_eq!(task.depends_on, vec!["Dep1", "Dep2"]);
2283        assert_eq!(task.task_id, Some(42));
2284    }
2285}
2286
2287#[cfg(test)]
2288mod dataflow_tests {
2289    use super::*;
2290    use crate::tasks::TaskManager;
2291    use crate::test_utils::test_helpers::TestContext;
2292
2293    #[tokio::test]
2294    async fn test_complete_dataflow_status_and_active_form() {
2295        // 创建测试环境
2296        let ctx = TestContext::new().await;
2297
2298        // 第1步:使用Plan工具创建带status和active_form的任务
2299        let request = PlanRequest {
2300            tasks: vec![TaskTree {
2301                name: "Test Active Form Task".to_string(),
2302                spec: Some("Testing complete dataflow".to_string()),
2303                priority: Some(PriorityValue::High),
2304                children: None,
2305                depends_on: None,
2306                task_id: None,
2307                status: Some(TaskStatus::Doing),
2308                active_form: Some("Testing complete dataflow now".to_string()),
2309            }],
2310        };
2311
2312        let executor = PlanExecutor::new(&ctx.pool);
2313        let result = executor.execute(&request).await.unwrap();
2314
2315        assert!(result.success);
2316        assert_eq!(result.created_count, 1);
2317
2318        // 第2步:使用TaskManager读取任务(模拟MCP task_list工具)
2319        let task_mgr = TaskManager::new(&ctx.pool);
2320        let result = task_mgr
2321            .find_tasks(None, None, None, None, None)
2322            .await
2323            .unwrap();
2324
2325        assert_eq!(result.tasks.len(), 1);
2326        let task = &result.tasks[0];
2327
2328        // 第3步:验证所有字段都正确传递
2329        assert_eq!(task.name, "Test Active Form Task");
2330        assert_eq!(task.status, "doing"); // InProgress maps to "doing"
2331        assert_eq!(
2332            task.active_form,
2333            Some("Testing complete dataflow now".to_string())
2334        );
2335
2336        // 第4步:验证序列化为JSON(模拟MCP输出)
2337        let json = serde_json::to_value(task).unwrap();
2338        assert_eq!(json["name"], "Test Active Form Task");
2339        assert_eq!(json["status"], "doing");
2340        assert_eq!(json["active_form"], "Testing complete dataflow now");
2341
2342        println!("✅ 完整数据流验证成功!");
2343        println!("   Plan工具写入 -> Task读取 -> JSON序列化 -> MCP输出");
2344        println!("   active_form: {:?}", task.active_form);
2345    }
2346}