Skip to main content

max / goingson

14.7 KB · 414 lines History Blame Raw
1 //! Integration tests for SqliteTaskRepository.
2
3 mod common;
4
5 use chrono::{Duration, Utc};
6 use goingson_core::{NewTask, Priority, Recurrence, TaskRepository, TaskStatus, UpdateTask};
7 use goingson_db_sqlite::SqliteTaskRepository;
8
9 #[tokio::test]
10 async fn test_create_and_get_task() {
11 let pool = common::setup_test_db().await;
12 let user_id = common::create_test_user(&pool).await;
13 let repo = SqliteTaskRepository::new(pool);
14
15 let new_task = NewTask::builder("Test task")
16 .priority(Priority::Medium)
17 .tag("test")
18 .build();
19
20 let created = repo.create(user_id, new_task).await.expect("Failed to create task");
21 assert_eq!(created.description, "Test task");
22 assert_eq!(created.status, TaskStatus::Pending);
23 assert_eq!(created.priority, Priority::Medium);
24 assert_eq!(created.tags, vec!["test"]);
25
26 let fetched = repo.get_by_id(created.id, user_id).await.expect("Failed to get task");
27 assert!(fetched.is_some());
28 assert_eq!(fetched.unwrap().id, created.id);
29 }
30
31 #[tokio::test]
32 async fn test_task_status_transitions() {
33 let pool = common::setup_test_db().await;
34 let user_id = common::create_test_user(&pool).await;
35 let repo = SqliteTaskRepository::new(pool);
36
37 let new_task = NewTask::builder("Status test")
38 .priority(Priority::High)
39 .build();
40
41 let task = repo.create(user_id, new_task).await.expect("Failed to create");
42 assert_eq!(task.status, TaskStatus::Pending);
43
44 // Start the task
45 let started = repo.start(task.id, user_id).await.expect("Failed to start");
46 assert!(started);
47
48 // Verify status changed
49 let fetched = repo.get_by_id(task.id, user_id).await.expect("Failed to get");
50 assert_eq!(fetched.unwrap().status, TaskStatus::Started);
51
52 // Complete the task
53 let completed = repo.complete(task.id, user_id).await.expect("Failed to complete");
54 assert!(completed.is_some());
55
56 // Verify status changed
57 let fetched = repo.get_by_id(task.id, user_id).await.expect("Failed to get");
58 assert_eq!(fetched.unwrap().status, TaskStatus::Completed);
59 }
60
61 #[tokio::test]
62 async fn test_task_snooze() {
63 let pool = common::setup_test_db().await;
64 let user_id = common::create_test_user(&pool).await;
65 let repo = SqliteTaskRepository::new(pool);
66
67 let new_task = NewTask::builder("Snooze test")
68 .priority(Priority::Low)
69 .build();
70
71 let task = repo.create(user_id, new_task).await.expect("Failed to create");
72
73 let until = Utc::now() + Duration::hours(2);
74 let snoozed = repo.snooze(task.id, user_id, until).await.expect("Failed to snooze");
75 assert!(snoozed.is_some());
76 assert!(snoozed.as_ref().unwrap().snoozed_until.is_some());
77
78 // Should appear in snoozed list
79 let snoozed_tasks = repo.list_snoozed(user_id).await.expect("Failed to list snoozed");
80 assert_eq!(snoozed_tasks.len(), 1);
81
82 // Unsnooze
83 let unsnoozed = repo.unsnooze(task.id, user_id).await.expect("Failed to unsnooze");
84 assert!(unsnoozed.is_some());
85 assert!(unsnoozed.unwrap().snoozed_until.is_none());
86 }
87
88 #[tokio::test]
89 async fn test_task_waiting_for_response() {
90 let pool = common::setup_test_db().await;
91 let user_id = common::create_test_user(&pool).await;
92 let repo = SqliteTaskRepository::new(pool);
93
94 let new_task = NewTask::builder("Waiting test")
95 .priority(Priority::Medium)
96 .build();
97
98 let task = repo.create(user_id, new_task).await.expect("Failed to create");
99
100 let expected = Utc::now() + Duration::days(3);
101 let waiting = repo.mark_waiting(task.id, user_id, Some(expected)).await.expect("Failed to mark waiting");
102 assert!(waiting.is_some());
103 assert!(waiting.as_ref().unwrap().waiting_for_response);
104
105 // Should appear in waiting list
106 let waiting_tasks = repo.list_waiting(user_id).await.expect("Failed to list waiting");
107 assert_eq!(waiting_tasks.len(), 1);
108
109 // Clear waiting
110 let cleared = repo.clear_waiting(task.id, user_id).await.expect("Failed to clear");
111 assert!(cleared.is_some());
112 assert!(!cleared.unwrap().waiting_for_response);
113 }
114
115 #[tokio::test]
116 async fn test_task_with_due_date() {
117 let pool = common::setup_test_db().await;
118 let user_id = common::create_test_user(&pool).await;
119 let repo = SqliteTaskRepository::new(pool);
120
121 let due = Utc::now() + Duration::days(1);
122 let new_task = NewTask::builder("Due tomorrow")
123 .priority(Priority::High)
124 .due(due)
125 .tag("urgent")
126 .urgency(8.5) // High priority task with urgency set
127 .build();
128
129 let task = repo.create(user_id, new_task).await.expect("Failed to create");
130 assert!(task.due.is_some());
131 // Urgency should be stored as provided
132 assert!((task.urgency - 8.5).abs() < 0.01);
133 }
134
135 #[tokio::test]
136 async fn test_delete_task() {
137 let pool = common::setup_test_db().await;
138 let user_id = common::create_test_user(&pool).await;
139 let repo = SqliteTaskRepository::new(pool);
140
141 let new_task = NewTask::builder("To delete")
142 .priority(Priority::Low)
143 .build();
144
145 let task = repo.create(user_id, new_task).await.expect("Failed to create");
146
147 let deleted = repo.delete(task.id, user_id).await.expect("Failed to delete");
148 assert!(deleted);
149
150 // Task should not appear in normal list (deleted tasks are filtered out)
151 let tasks = repo.list_all(user_id).await.expect("Failed to list");
152 assert!(tasks.iter().all(|t| t.id != task.id));
153 }
154
155 #[tokio::test]
156 async fn test_task_urgency_ordering() {
157 let pool = common::setup_test_db().await;
158 let user_id = common::create_test_user(&pool).await;
159 let repo = SqliteTaskRepository::new(pool);
160
161 // Create low urgency task
162 let low = NewTask::builder("Low urgency")
163 .priority(Priority::Low)
164 .urgency(2.0)
165 .build();
166 repo.create(user_id, low).await.expect("Failed to create");
167
168 // Create high urgency task
169 let high = NewTask::builder("High urgency")
170 .priority(Priority::High)
171 .urgency(8.0)
172 .build();
173 repo.create(user_id, high).await.expect("Failed to create");
174
175 // Create highest urgency task (overdue)
176 let overdue = NewTask::builder("Overdue task")
177 .priority(Priority::Medium)
178 .due(Utc::now() - Duration::days(1))
179 .urgency(15.0) // Overdue penalty applied
180 .build();
181 repo.create(user_id, overdue).await.expect("Failed to create");
182
183 let tasks = repo.list_all(user_id).await.expect("Failed to list");
184 assert_eq!(tasks.len(), 3);
185
186 // Tasks should be ordered by urgency descending
187 assert!(tasks[0].urgency >= tasks[1].urgency);
188 assert!(tasks[1].urgency >= tasks[2].urgency);
189 // Verify the highest urgency task is first
190 assert_eq!(tasks[0].description, "Overdue task");
191 }
192
193 #[tokio::test]
194 async fn test_complete_sets_completed_at() {
195 let pool = common::setup_test_db().await;
196 let user_id = common::create_test_user(&pool).await;
197 let repo = SqliteTaskRepository::new(pool);
198
199 let new_task = NewTask::builder("Complete me")
200 .priority(Priority::Medium)
201 .build();
202
203 let task = repo.create(user_id, new_task).await.expect("Failed to create");
204 assert!(task.completed_at.is_none());
205
206 // Complete the task
207 let completed = repo.complete(task.id, user_id).await.expect("Failed to complete");
208 assert!(completed.is_some());
209 let completed = completed.unwrap();
210 assert_eq!(completed.status, TaskStatus::Completed);
211 assert!(completed.completed_at.is_some(), "completed_at should be set after completing");
212 }
213
214 #[tokio::test]
215 async fn test_list_completed_between_filters_by_date() {
216 let pool = common::setup_test_db().await;
217 let user_id = common::create_test_user(&pool).await;
218 let repo = SqliteTaskRepository::new(pool);
219
220 // Create and complete two tasks
221 let task1 = NewTask::builder("Task one")
222 .priority(Priority::Medium)
223 .build();
224 let t1 = repo.create(user_id, task1).await.expect("Failed to create");
225 repo.complete(t1.id, user_id).await.expect("Failed to complete");
226
227 let task2 = NewTask::builder("Task two")
228 .priority(Priority::Low)
229 .build();
230 let t2 = repo.create(user_id, task2).await.expect("Failed to create");
231 repo.complete(t2.id, user_id).await.expect("Failed to complete");
232
233 // Query with a range that includes now
234 let start = Utc::now() - Duration::minutes(5);
235 let end = Utc::now() + Duration::minutes(5);
236 let results = repo.list_completed_between(user_id, start, end).await.expect("Failed to list");
237 assert_eq!(results.len(), 2, "Both tasks should be in the date range");
238
239 // Query with a range in the past — should return nothing
240 let old_start = Utc::now() - Duration::days(30);
241 let old_end = Utc::now() - Duration::days(29);
242 let results = repo.list_completed_between(user_id, old_start, old_end).await.expect("Failed to list");
243 assert!(results.is_empty(), "No tasks should match a past date range");
244 }
245
246 #[tokio::test]
247 async fn test_update_to_completed_sets_completed_at() {
248 let pool = common::setup_test_db().await;
249 let user_id = common::create_test_user(&pool).await;
250 let repo = SqliteTaskRepository::new(pool);
251
252 let new_task = NewTask::builder("Update to completed")
253 .priority(Priority::Medium)
254 .build();
255
256 let task = repo.create(user_id, new_task).await.expect("Failed to create");
257 assert!(task.completed_at.is_none());
258
259 // Update status to Completed via update()
260 let update = UpdateTask {
261 project_id: None,
262 milestone_id: None,
263 contact_id: None,
264 description: task.description.clone(),
265 status: TaskStatus::Completed,
266 priority: task.priority.clone(),
267 due: task.due,
268 tags: task.tags.clone(),
269 recurrence: Recurrence::None,
270 urgency: task.urgency,
271 scheduled_start: None,
272 scheduled_duration: None,
273 estimated_minutes: None,
274 };
275
276 let updated = repo.update(task.id, user_id, update).await.expect("Failed to update");
277 assert!(updated.is_some());
278 let updated = updated.unwrap();
279 assert_eq!(updated.status, TaskStatus::Completed);
280 assert!(updated.completed_at.is_some(), "completed_at should be set when updating to Completed");
281 }
282
283 #[tokio::test]
284 async fn test_update_from_completed_clears_completed_at() {
285 let pool = common::setup_test_db().await;
286 let user_id = common::create_test_user(&pool).await;
287 let repo = SqliteTaskRepository::new(pool);
288
289 let new_task = NewTask::builder("Revert from completed")
290 .priority(Priority::High)
291 .build();
292
293 let task = repo.create(user_id, new_task).await.expect("Failed to create");
294
295 // Complete it first
296 let completed = repo.complete(task.id, user_id).await.expect("Failed to complete");
297 assert!(completed.is_some());
298 assert!(completed.unwrap().completed_at.is_some());
299
300 // Now move it back to Pending via update()
301 let update = UpdateTask {
302 project_id: None,
303 milestone_id: None,
304 contact_id: None,
305 description: task.description.clone(),
306 status: TaskStatus::Pending,
307 priority: task.priority.clone(),
308 due: task.due,
309 tags: task.tags.clone(),
310 recurrence: Recurrence::None,
311 urgency: task.urgency,
312 scheduled_start: None,
313 scheduled_duration: None,
314 estimated_minutes: None,
315 };
316
317 let reverted = repo.update(task.id, user_id, update).await.expect("Failed to update");
318 assert!(reverted.is_some());
319 let reverted = reverted.unwrap();
320 assert_eq!(reverted.status, TaskStatus::Pending);
321 assert!(reverted.completed_at.is_none(), "completed_at should be cleared when moving away from Completed");
322 }
323
324 #[tokio::test]
325 async fn test_snooze_completed_task_fails() {
326 let pool = common::setup_test_db().await;
327 let user_id = common::create_test_user(&pool).await;
328 let repo = SqliteTaskRepository::new(pool);
329
330 let new_task = NewTask::builder("Completed task")
331 .priority(Priority::Medium)
332 .build();
333
334 let task = repo.create(user_id, new_task).await.expect("Failed to create");
335
336 // Complete the task
337 repo.complete(task.id, user_id).await.expect("Failed to complete");
338
339 // Attempt to snooze the completed task — should fail
340 let until = Utc::now() + Duration::hours(2);
341 let result = repo.snooze(task.id, user_id, until).await;
342 assert!(result.is_err(), "Snoozing a completed task should return an error");
343 let err = result.unwrap_err();
344 assert!(err.is_validation(), "Error should be a validation error, got: {}", err);
345 }
346
347 #[tokio::test]
348 async fn test_snooze_deleted_task_fails() {
349 let pool = common::setup_test_db().await;
350 let user_id = common::create_test_user(&pool).await;
351 let repo = SqliteTaskRepository::new(pool);
352
353 let new_task = NewTask::builder("Deleted task")
354 .priority(Priority::Medium)
355 .build();
356
357 let task = repo.create(user_id, new_task).await.expect("Failed to create");
358
359 // Delete the task
360 repo.delete(task.id, user_id).await.expect("Failed to delete");
361
362 // Attempt to snooze the deleted task — should fail
363 let until = Utc::now() + Duration::hours(2);
364 let result = repo.snooze(task.id, user_id, until).await;
365 assert!(result.is_err(), "Snoozing a deleted task should return an error");
366 let err = result.unwrap_err();
367 assert!(err.is_validation(), "Error should be a validation error, got: {}", err);
368 }
369
370 #[tokio::test]
371 async fn test_snooze_pending_task_succeeds() {
372 let pool = common::setup_test_db().await;
373 let user_id = common::create_test_user(&pool).await;
374 let repo = SqliteTaskRepository::new(pool);
375
376 let new_task = NewTask::builder("Pending task")
377 .priority(Priority::Medium)
378 .build();
379
380 let task = repo.create(user_id, new_task).await.expect("Failed to create");
381
382 // Snooze the pending task — should succeed
383 let until = Utc::now() + Duration::hours(2);
384 let result = repo.snooze(task.id, user_id, until).await;
385 assert!(result.is_ok(), "Snoozing a pending task should succeed");
386 let snoozed = result.unwrap();
387 assert!(snoozed.is_some());
388 assert!(snoozed.unwrap().snoozed_until.is_some());
389 }
390
391 #[tokio::test]
392 async fn test_snooze_started_task_succeeds() {
393 let pool = common::setup_test_db().await;
394 let user_id = common::create_test_user(&pool).await;
395 let repo = SqliteTaskRepository::new(pool);
396
397 let new_task = NewTask::builder("Started task")
398 .priority(Priority::Medium)
399 .build();
400
401 let task = repo.create(user_id, new_task).await.expect("Failed to create");
402
403 // Start the task first
404 repo.start(task.id, user_id).await.expect("Failed to start");
405
406 // Snooze the started task — should succeed
407 let until = Utc::now() + Duration::hours(2);
408 let result = repo.snooze(task.id, user_id, until).await;
409 assert!(result.is_ok(), "Snoozing a started task should succeed");
410 let snoozed = result.unwrap();
411 assert!(snoozed.is_some());
412 assert!(snoozed.unwrap().snoozed_until.is_some());
413 }
414