track/use_cases/
complete_todo.rs1use crate::db::Database;
2use crate::models::TodoStatus;
3use crate::services::{TodoService, WorktreeService};
4use crate::utils::{Result, TrackError};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct CompleteTodoOutcome {
9 pub task_index: i64,
10 pub merged_bookmark: Option<String>,
11}
12
13pub struct CompleteTodoUseCase<'a> {
20 db: &'a Database,
21}
22
23impl<'a> CompleteTodoUseCase<'a> {
24 pub fn new(db: &'a Database) -> Self {
25 Self { db }
26 }
27
28 pub fn execute(&self, task_id: i64, task_index: i64) -> Result<CompleteTodoOutcome> {
30 let todo_service = TodoService::new(self.db);
31 let worktree_service = WorktreeService::new(self.db);
32
33 let todo = todo_service.get_todo_by_index(task_id, task_index)?;
34
35 if todo.status != TodoStatus::Pending {
36 return Err(TrackError::InvalidStatusTransition {
37 from: todo.status.as_str().to_string(),
38 to: TodoStatus::Done.as_str().to_string(),
39 });
40 }
41
42 let merged_bookmark = worktree_service.complete_worktree_for_todo(todo.id)?;
43
44 if let Err(err) = todo_service.mark_done(todo.id) {
45 if let Some(bookmark) = merged_bookmark.clone() {
46 return Err(TrackError::TodoCompletionDbFailed {
47 todo_index: task_index,
48 bookmark,
49 detail: err.to_string(),
50 });
51 }
52 return Err(err);
53 }
54
55 Ok(CompleteTodoOutcome {
56 task_index,
57 merged_bookmark,
58 })
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::db::Database;
66 use crate::services::TaskService;
67
68 fn setup_db() -> Database {
69 Database::new_in_memory().unwrap()
70 }
71
72 #[test]
73 fn complete_todo_rejects_already_done() {
74 let db = setup_db();
75 let task_id = TaskService::new(&db)
76 .create_task("Task", None, None, None)
77 .unwrap()
78 .id;
79 let todo_service = TodoService::new(&db);
80 let todo = todo_service.add_todo(task_id, "Item", false).unwrap();
81 todo_service.mark_done(todo.id).unwrap();
82
83 let result = CompleteTodoUseCase::new(&db).execute(task_id, todo.task_index);
84 assert!(matches!(
85 result,
86 Err(TrackError::InvalidStatusTransition { .. })
87 ));
88 }
89}