Skip to main content

max / goingson

5.5 KB · 190 lines History Blame Raw
1 //! Integration tests for milestone repository operations.
2
3 use chrono::NaiveDate;
4 use goingson_core::{MilestoneStatus, NewMilestone, NewTask, Priority, TaskStatus};
5
6 use crate::test_utils::{create_test_project, setup_test_state};
7
8 #[tokio::test]
9 async fn create_and_list_milestone() {
10 let (state, user_id) = setup_test_state().await;
11 let project_id = create_test_project(&state, user_id).await;
12
13 let target = NaiveDate::from_ymd_opt(2026, 8, 15).unwrap();
14 let created = state
15 .milestones
16 .create(
17 user_id,
18 NewMilestone {
19 project_id,
20 name: "Alpha Release".to_string(),
21 description: "Ship it".to_string(),
22 position: 0,
23 target_date: Some(target),
24 },
25 )
26 .await
27 .unwrap();
28
29 assert_eq!(created.name, "Alpha Release");
30 assert_eq!(created.description, "Ship it");
31 assert_eq!(created.target_date, Some(target));
32 assert_eq!(created.status, MilestoneStatus::Open);
33
34 let milestones = state
35 .milestones
36 .list_by_project(project_id, user_id)
37 .await
38 .unwrap();
39 assert_eq!(milestones.len(), 1);
40 assert_eq!(milestones[0].id, created.id);
41 }
42
43 #[tokio::test]
44 async fn update_milestone() {
45 let (state, user_id) = setup_test_state().await;
46 let project_id = create_test_project(&state, user_id).await;
47
48 let created = state
49 .milestones
50 .create(
51 user_id,
52 NewMilestone {
53 project_id,
54 name: "Original".to_string(),
55 description: "old".to_string(),
56 position: 0,
57 target_date: None,
58 },
59 )
60 .await
61 .unwrap();
62
63 let new_date = NaiveDate::from_ymd_opt(2026, 12, 31).unwrap();
64 let updated = state
65 .milestones
66 .update(
67 created.id,
68 user_id,
69 "Renamed",
70 "new desc",
71 Some(new_date),
72 &MilestoneStatus::Completed,
73 )
74 .await
75 .unwrap()
76 .unwrap();
77
78 assert_eq!(updated.name, "Renamed");
79 assert_eq!(updated.description, "new desc");
80 assert_eq!(updated.target_date, Some(new_date));
81 }
82
83 #[tokio::test]
84 async fn update_nonexistent_milestone_returns_none() {
85 let (state, user_id) = setup_test_state().await;
86
87 let fake_id = goingson_core::MilestoneId::from(uuid::Uuid::new_v4());
88 let result = state
89 .milestones
90 .update(fake_id, user_id, "Nope", "", None, &MilestoneStatus::Open)
91 .await
92 .unwrap();
93 assert!(result.is_none());
94 }
95
96 #[tokio::test]
97 async fn delete_milestone() {
98 let (state, user_id) = setup_test_state().await;
99 let project_id = create_test_project(&state, user_id).await;
100
101 let created = state
102 .milestones
103 .create(
104 user_id,
105 NewMilestone {
106 project_id,
107 name: "To Delete".to_string(),
108 description: String::new(),
109 position: 0,
110 target_date: None,
111 },
112 )
113 .await
114 .unwrap();
115
116 assert!(state.milestones.delete(created.id, user_id).await.unwrap());
117 assert!(
118 state
119 .milestones
120 .get_by_id(created.id, user_id)
121 .await
122 .unwrap()
123 .is_none()
124 );
125 // Deleting an already-gone milestone reports no rows affected.
126 assert!(!state.milestones.delete(created.id, user_id).await.unwrap());
127 }
128
129 /// Mirrors the progress fields the `list_milestones`/`update_milestone` commands
130 /// pre-compute: task_count, completed_count, and rounded percentage from the tasks
131 /// linked to a milestone.
132 #[tokio::test]
133 async fn milestone_progress_reflects_linked_tasks() {
134 let (state, user_id) = setup_test_state().await;
135 let project_id = create_test_project(&state, user_id).await;
136
137 let milestone = state
138 .milestones
139 .create(
140 user_id,
141 NewMilestone {
142 project_id,
143 name: "Progress".to_string(),
144 description: String::new(),
145 position: 0,
146 target_date: None,
147 },
148 )
149 .await
150 .unwrap();
151
152 // 4 tasks on the milestone, 3 complete; plus 1 unrelated task on the project.
153 for i in 0..4 {
154 let t = NewTask::builder(format!("Milestone task {i}"))
155 .project_id(project_id)
156 .milestone_id(milestone.id)
157 .priority(Priority::Medium)
158 .build();
159 let task = state.tasks.create(user_id, t).await.unwrap();
160 if i < 3 {
161 state.tasks.complete(task.id, user_id).await.unwrap();
162 }
163 }
164 let unrelated = NewTask::builder("Not on milestone")
165 .project_id(project_id)
166 .priority(Priority::Low)
167 .build();
168 state.tasks.create(user_id, unrelated).await.unwrap();
169
170 let all_tasks = state
171 .tasks
172 .list_by_project(user_id, project_id)
173 .await
174 .unwrap();
175 let milestone_tasks: Vec<_> = all_tasks
176 .iter()
177 .filter(|t| t.milestone_id == Some(milestone.id))
178 .collect();
179 let task_count = milestone_tasks.len();
180 let completed_count = milestone_tasks
181 .iter()
182 .filter(|t| t.status == TaskStatus::Completed)
183 .count();
184 let progress = ((completed_count as f64 / task_count as f64) * 100.0).round() as u8;
185
186 assert_eq!(task_count, 4);
187 assert_eq!(completed_count, 3);
188 assert_eq!(progress, 75);
189 }
190