Skip to main content

max / goingson

8.5 KB · 255 lines History Blame Raw
1 //! Integration tests for time tracking (time_sessions) on SqliteTaskRepository.
2
3 mod common;
4
5 use goingson_core::TaskRepository;
6 use goingson_db_sqlite::SqliteTaskRepository;
7
8 #[tokio::test]
9 async fn test_start_timer_creates_active_session() {
10 let pool = common::setup_test_db().await;
11 let user_id = common::create_test_user(&pool).await;
12 let task_id = common::create_test_task(&pool, user_id).await;
13 let repo = SqliteTaskRepository::new(pool);
14
15 let session = repo
16 .start_timer(task_id, user_id)
17 .await
18 .expect("Failed to start timer");
19
20 assert_eq!(session.task_id, task_id);
21 assert!(session.is_active());
22 assert!(session.ended_at.is_none());
23 assert!(session.duration_minutes.is_none());
24 }
25
26 #[tokio::test]
27 async fn test_start_timer_fails_if_already_active() {
28 let pool = common::setup_test_db().await;
29 let user_id = common::create_test_user(&pool).await;
30 let task_id = common::create_test_task(&pool, user_id).await;
31 let repo = SqliteTaskRepository::new(pool);
32
33 repo.start_timer(task_id, user_id)
34 .await
35 .expect("First start should succeed");
36
37 let result = repo.start_timer(task_id, user_id).await;
38 assert!(result.is_err(), "Second start should fail");
39 let err = result.unwrap_err().to_string();
40 assert!(
41 err.contains("already running"),
42 "Error should mention already running: {err}"
43 );
44 }
45
46 #[tokio::test]
47 async fn test_stop_timer_sets_ended_at_and_duration() {
48 let pool = common::setup_test_db().await;
49 let user_id = common::create_test_user(&pool).await;
50 let task_id = common::create_test_task(&pool, user_id).await;
51 let repo = SqliteTaskRepository::new(pool);
52
53 repo.start_timer(task_id, user_id)
54 .await
55 .expect("Failed to start timer");
56
57 let stopped = repo
58 .stop_timer(task_id, user_id)
59 .await
60 .expect("Failed to stop timer");
61
62 assert!(stopped.is_some(), "Should return the stopped session");
63 let session = stopped.unwrap();
64 assert!(!session.is_active());
65 assert!(session.ended_at.is_some());
66 assert!(session.duration_minutes.is_some());
67 }
68
69 #[tokio::test]
70 async fn test_stop_timer_updates_actual_minutes_cache() {
71 let pool = common::setup_test_db().await;
72 let user_id = common::create_test_user(&pool).await;
73 let task_id = common::create_test_task(&pool, user_id).await;
74 let repo = SqliteTaskRepository::new(pool);
75
76 repo.start_timer(task_id, user_id)
77 .await
78 .expect("Failed to start timer");
79
80 repo.stop_timer(task_id, user_id)
81 .await
82 .expect("Failed to stop timer");
83
84 // Fetch the task and check actual_minutes is updated
85 let task = repo
86 .get_by_id(task_id, user_id)
87 .await
88 .expect("Failed to get task");
89 assert!(task.is_some(), "Task should exist");
90 // actual_minutes should be >= 0 (the session was nearly instant)
91 assert!(task.unwrap().actual_minutes >= 0);
92 }
93
94 #[tokio::test]
95 async fn test_discard_timer_removes_session() {
96 let pool = common::setup_test_db().await;
97 let user_id = common::create_test_user(&pool).await;
98 let task_id = common::create_test_task(&pool, user_id).await;
99 let repo = SqliteTaskRepository::new(pool);
100
101 repo.start_timer(task_id, user_id)
102 .await
103 .expect("Failed to start timer");
104
105 let discarded = repo
106 .discard_timer(task_id, user_id)
107 .await
108 .expect("Failed to discard timer");
109 assert!(discarded, "Should return true when session was discarded");
110
111 // No active timer should remain
112 let active = repo
113 .get_active_timer(user_id)
114 .await
115 .expect("Failed to get active timer");
116 assert!(active.is_none(), "No active timer after discard");
117
118 // actual_minutes should NOT have been updated
119 let task = repo
120 .get_by_id(task_id, user_id)
121 .await
122 .expect("Failed to get task")
123 .unwrap();
124 assert_eq!(task.actual_minutes, 0, "Discard should not update actual_minutes");
125 }
126
127 #[tokio::test]
128 async fn test_get_active_timer_returns_task_description() {
129 let pool = common::setup_test_db().await;
130 let user_id = common::create_test_user(&pool).await;
131 let task_id = common::create_test_task(&pool, user_id).await;
132 let repo = SqliteTaskRepository::new(pool);
133
134 repo.start_timer(task_id, user_id)
135 .await
136 .expect("Failed to start timer");
137
138 let active = repo
139 .get_active_timer(user_id)
140 .await
141 .expect("Failed to get active timer");
142 assert!(active.is_some(), "Should have an active timer");
143
144 let (session, description) = active.unwrap();
145 assert_eq!(session.task_id, task_id);
146 assert!(!description.is_empty(), "Should include task description");
147 }
148
149 #[tokio::test]
150 async fn test_list_time_sessions_for_task() {
151 let pool = common::setup_test_db().await;
152 let user_id = common::create_test_user(&pool).await;
153 let task_id = common::create_test_task(&pool, user_id).await;
154 let repo = SqliteTaskRepository::new(pool);
155
156 // Session 1: start and stop
157 repo.start_timer(task_id, user_id).await.unwrap();
158 repo.stop_timer(task_id, user_id).await.unwrap();
159
160 // Session 2: start and stop
161 repo.start_timer(task_id, user_id).await.unwrap();
162 repo.stop_timer(task_id, user_id).await.unwrap();
163
164 let sessions = repo
165 .list_time_sessions(task_id, user_id)
166 .await
167 .expect("Failed to list sessions");
168 assert_eq!(sessions.len(), 2, "Should have 2 completed sessions");
169 assert!(
170 sessions.iter().all(|s| !s.is_active()),
171 "All sessions should be stopped"
172 );
173 }
174
175 #[tokio::test]
176 async fn test_multiple_sessions_accumulate_actual_minutes() {
177 let pool = common::setup_test_db().await;
178 let user_id = common::create_test_user(&pool).await;
179 let task_id = common::create_test_task(&pool, user_id).await;
180 let repo = SqliteTaskRepository::new(pool);
181
182 // Session 1
183 repo.start_timer(task_id, user_id).await.unwrap();
184 repo.stop_timer(task_id, user_id).await.unwrap();
185
186 // Session 2
187 repo.start_timer(task_id, user_id).await.unwrap();
188 repo.stop_timer(task_id, user_id).await.unwrap();
189
190 let task = repo.get_by_id(task_id, user_id).await.unwrap().unwrap();
191 // Both sessions were near-instant so actual_minutes >= 0
192 // The important thing is it didn't error and the cache works
193 assert!(task.actual_minutes >= 0);
194 }
195
196 #[tokio::test]
197 async fn test_get_time_summary() {
198 let pool = common::setup_test_db().await;
199 let user_id = common::create_test_user(&pool).await;
200 let task_id = common::create_test_task(&pool, user_id).await;
201 let repo = SqliteTaskRepository::new(pool);
202
203 // Create a completed session
204 repo.start_timer(task_id, user_id).await.unwrap();
205 repo.stop_timer(task_id, user_id).await.unwrap();
206
207 let now = chrono::Utc::now();
208 let week_ago = now - chrono::Duration::days(7);
209 let week_ahead = now + chrono::Duration::days(7);
210
211 let summaries = repo
212 .get_time_summary(user_id, week_ago, week_ahead)
213 .await
214 .expect("Failed to get time summary");
215
216 assert!(!summaries.is_empty(), "Should have at least one summary entry");
217 assert!(summaries[0].session_count > 0, "Should have session count");
218 }
219
220 #[tokio::test]
221 async fn test_estimated_minutes_persists_on_create() {
222 let pool = common::setup_test_db().await;
223 let user_id = common::create_test_user(&pool).await;
224 let repo = SqliteTaskRepository::new(pool);
225
226 // Create task with estimated_minutes
227 let new_task = goingson_core::NewTaskBuilder::new("Estimated task".to_string())
228 .estimated_minutes(45)
229 .build();
230
231 let task = repo.create(user_id, new_task).await.expect("Failed to create task");
232 assert_eq!(task.estimated_minutes, Some(45));
233 assert_eq!(task.actual_minutes, 0);
234
235 // Fetch it back to verify persistence
236 let fetched = repo.get_by_id(task.id, user_id).await.unwrap().unwrap();
237 assert_eq!(fetched.estimated_minutes, Some(45));
238 }
239
240 #[tokio::test]
241 async fn test_task_with_active_session_has_active_session_populated() {
242 let pool = common::setup_test_db().await;
243 let user_id = common::create_test_user(&pool).await;
244 let task_id = common::create_test_task(&pool, user_id).await;
245 let repo = SqliteTaskRepository::new(pool);
246
247 // Start a timer
248 repo.start_timer(task_id, user_id).await.unwrap();
249
250 // Fetch the task — active_session should be populated
251 let task = repo.get_by_id(task_id, user_id).await.unwrap().unwrap();
252 assert!(task.active_session.is_some(), "Task with running timer should have active_session");
253 assert!(task.has_active_timer());
254 }
255