Skip to main content

max / goingson

6.5 KB · 219 lines History Blame Raw
1 //! Integration tests for day planning commands.
2 //!
3 //! Tests schedule/unschedule operations through the repository layer,
4 //! since day_planning commands are thin wrappers that require Tauri `State<'_>`.
5
6 use chrono::{Duration, Utc};
7
8 use goingson_core::{NewEvent, NewTask, Priority, TaskId};
9
10 use crate::test_utils::{create_test_project, setup_test_state};
11
12 // ============ Schedule Task ============
13
14 #[tokio::test]
15 async fn test_schedule_task_success() {
16 let (state, user_id) = setup_test_state().await;
17 let project_id = create_test_project(&state, user_id).await;
18
19 let new_task = NewTask::builder("Plan sprint")
20 .project_id(project_id)
21 .priority(Priority::High)
22 .build();
23 let task = state.tasks.create(user_id, new_task).await.unwrap();
24
25 // Schedule the task (mirrors what schedule_task command does)
26 let start_time = Utc::now() + Duration::hours(2);
27 let duration = 45; // minutes
28
29 let scheduled = state
30 .tasks
31 .update_schedule(task.id, user_id, Some(start_time), Some(duration))
32 .await
33 .unwrap();
34
35 assert!(scheduled.is_some());
36 let scheduled = scheduled.unwrap();
37 // SQLite stores timestamps at second precision, so compare within 1 second
38 let stored_start = scheduled.scheduled_start.unwrap();
39 assert!((stored_start - start_time).num_seconds().abs() <= 1);
40 assert_eq!(scheduled.scheduled_duration, Some(duration));
41
42 // Create the linked event (mirrors schedule_task command behavior)
43 let end_time = start_time + Duration::minutes(duration as i64);
44 let event = NewEvent::builder(&task.description, start_time)
45 .end_time(end_time)
46 .project_id(project_id)
47 .linked_task_id(task.id)
48 .build();
49
50 let created_event = state.events.create(user_id, event).await.unwrap();
51 assert_eq!(created_event.title, "Plan sprint");
52 assert_eq!(created_event.linked_task_id, Some(task.id));
53 assert_eq!(created_event.project_id, Some(project_id));
54
55 // Verify event is linked to task
56 let linked = state
57 .events
58 .get_by_linked_task(user_id, task.id)
59 .await
60 .unwrap();
61 assert!(linked.is_some());
62 assert_eq!(linked.unwrap().title, "Plan sprint");
63 }
64
65 #[tokio::test]
66 async fn test_schedule_task_not_found() {
67 let (state, user_id) = setup_test_state().await;
68
69 let nonexistent_id = TaskId::new();
70 let start_time = Utc::now() + Duration::hours(1);
71
72 // update_schedule returns None for nonexistent task
73 let result = state
74 .tasks
75 .update_schedule(nonexistent_id, user_id, Some(start_time), Some(30))
76 .await
77 .unwrap();
78
79 assert!(result.is_none());
80 }
81
82 // ============ Unschedule Task ============
83
84 #[tokio::test]
85 async fn test_unschedule_task_success() {
86 let (state, user_id) = setup_test_state().await;
87
88 let new_task = NewTask::builder("Scheduled then removed")
89 .priority(Priority::Medium)
90 .build();
91 let task = state.tasks.create(user_id, new_task).await.unwrap();
92
93 // Schedule it
94 let start_time = Utc::now() + Duration::hours(3);
95 state
96 .tasks
97 .update_schedule(task.id, user_id, Some(start_time), Some(60))
98 .await
99 .unwrap();
100
101 // Create linked event
102 let event = NewEvent::builder("Scheduled then removed", start_time)
103 .linked_task_id(task.id)
104 .build();
105 state.events.create(user_id, event).await.unwrap();
106
107 // Unschedule (mirrors unschedule_task command)
108 state
109 .events
110 .delete_by_linked_task(user_id, task.id)
111 .await
112 .unwrap();
113
114 let cleared = state
115 .tasks
116 .update_schedule(task.id, user_id, None, None)
117 .await
118 .unwrap();
119
120 assert!(cleared.is_some());
121 let cleared = cleared.unwrap();
122 assert!(cleared.scheduled_start.is_none());
123 assert!(cleared.scheduled_duration.is_none());
124
125 // Verify linked event is gone
126 let linked = state
127 .events
128 .get_by_linked_task(user_id, task.id)
129 .await
130 .unwrap();
131 assert!(linked.is_none());
132 }
133
134 #[tokio::test]
135 async fn test_unschedule_task_not_found() {
136 let (state, user_id) = setup_test_state().await;
137
138 let nonexistent_id = TaskId::new();
139
140 // delete_by_linked_task returns false for nonexistent task link
141 let deleted = state
142 .events
143 .delete_by_linked_task(user_id, nonexistent_id)
144 .await
145 .unwrap();
146 assert!(!deleted);
147
148 // update_schedule returns None for nonexistent task
149 let result = state
150 .tasks
151 .update_schedule(nonexistent_id, user_id, None, None)
152 .await
153 .unwrap();
154 assert!(result.is_none());
155 }
156
157 // ============ Day Plan View ============
158
159 #[tokio::test]
160 async fn test_get_day_plan_empty() {
161 let (state, user_id) = setup_test_state().await;
162
163 // Query for a date with no events or tasks
164 let date = (Utc::now() + Duration::days(30)).date_naive();
165
166 let events = state.events.list_for_date(user_id, date).await.unwrap();
167 assert!(events.is_empty());
168
169 let unscheduled = state
170 .tasks
171 .list_unscheduled_due_on_date(user_id, date)
172 .await
173 .unwrap();
174 assert!(unscheduled.is_empty());
175 }
176
177 #[tokio::test]
178 async fn test_get_day_plan_with_events() {
179 let (state, user_id) = setup_test_state().await;
180
181 // Create an event for tomorrow
182 let tomorrow = (Utc::now() + Duration::days(1)).date_naive();
183 let start = tomorrow
184 .and_hms_opt(10, 0, 0)
185 .unwrap();
186 let start_utc = chrono::DateTime::<Utc>::from_naive_utc_and_offset(start, Utc);
187
188 let event = NewEvent::builder("Morning standup", start_utc)
189 .end_time(start_utc + Duration::hours(1))
190 .build();
191 state.events.create(user_id, event).await.unwrap();
192
193 let events = state.events.list_for_date(user_id, tomorrow).await.unwrap();
194 assert_eq!(events.len(), 1);
195 assert_eq!(events[0].title, "Morning standup");
196 }
197
198 #[tokio::test]
199 async fn test_get_day_plan_with_due_task() {
200 let (state, user_id) = setup_test_state().await;
201
202 // Create a task due tomorrow with no schedule
203 let tomorrow = Utc::now() + Duration::days(1);
204 let task = NewTask::builder("Due tomorrow")
205 .priority(Priority::High)
206 .due(tomorrow)
207 .build();
208 state.tasks.create(user_id, task).await.unwrap();
209
210 let date = tomorrow.date_naive();
211 let unscheduled = state
212 .tasks
213 .list_unscheduled_due_on_date(user_id, date)
214 .await
215 .unwrap();
216 assert_eq!(unscheduled.len(), 1);
217 assert_eq!(unscheduled[0].description, "Due tomorrow");
218 }
219