Skip to main content

max / goingson

15.2 KB · 452 lines History Blame Raw
1 //! Integration tests for SqliteEventRepository.
2
3 mod common;
4
5 use chrono::{Duration, NaiveDate, Utc};
6 use goingson_core::{EventRepository, NewEvent, Recurrence};
7 use goingson_db_sqlite::SqliteEventRepository;
8
9 #[tokio::test]
10 async fn test_create_and_get_event() {
11 let pool = common::setup_test_db().await;
12 let user_id = common::create_test_user(&pool).await;
13 let repo = SqliteEventRepository::new(pool);
14
15 let start = Utc::now() + Duration::hours(2);
16 let end = start + Duration::hours(1);
17
18 let new_event = NewEvent {
19 user_id: Some(user_id),
20 project_id: None,
21 title: "Team Meeting".to_string(),
22 description: "Weekly standup".to_string(),
23 start_time: start,
24 end_time: Some(end),
25 location: Some("Conference Room A".to_string()),
26 linked_task_id: None,
27 recurrence: Recurrence::None,
28 recurrence_rule: None,
29 contact_id: None,
30 block_type: None,
31 reminder_offsets_seconds: Vec::new(),
32 };
33
34 let created = repo.create(user_id, new_event).await.expect("Failed to create event");
35 assert_eq!(created.title, "Team Meeting");
36 assert_eq!(created.location, Some("Conference Room A".to_string()));
37
38 let fetched = repo.get_by_id(created.id, user_id).await.expect("Failed to get event");
39 assert!(fetched.is_some());
40 assert_eq!(fetched.unwrap().id, created.id);
41 }
42
43 #[tokio::test]
44 async fn test_get_upcoming_events() {
45 let pool = common::setup_test_db().await;
46 let user_id = common::create_test_user(&pool).await;
47 let repo = SqliteEventRepository::new(pool);
48
49 // Create past event
50 let past = NewEvent {
51 user_id: Some(user_id),
52 project_id: None,
53 title: "Past Event".to_string(),
54 description: String::new(),
55 start_time: Utc::now() - Duration::days(2),
56 end_time: None,
57 location: None,
58 linked_task_id: None,
59 recurrence: Recurrence::None,
60 recurrence_rule: None,
61 contact_id: None,
62 block_type: None,
63 reminder_offsets_seconds: Vec::new(),
64 };
65 repo.create(user_id, past).await.expect("Failed to create");
66
67 // Create upcoming events
68 for i in 1..=3 {
69 let future = NewEvent {
70 user_id: Some(user_id),
71 project_id: None,
72 title: format!("Future Event {}", i),
73 description: String::new(),
74 start_time: Utc::now() + Duration::days(i as i64),
75 end_time: None,
76 location: None,
77 linked_task_id: None,
78 recurrence: Recurrence::None,
79 recurrence_rule: None,
80 contact_id: None,
81 block_type: None,
82 reminder_offsets_seconds: Vec::new(),
83 };
84 repo.create(user_id, future).await.expect("Failed to create");
85 }
86
87 // Get events in next 7 days
88 let upcoming = repo.get_upcoming(user_id, 7).await.expect("Failed to get upcoming");
89 assert_eq!(upcoming.len(), 3);
90
91 // All should be in the future
92 for event in &upcoming {
93 assert!(event.start_time > Utc::now());
94 }
95 }
96
97 #[tokio::test]
98 async fn test_list_events_for_date() {
99 let pool = common::setup_test_db().await;
100 let user_id = common::create_test_user(&pool).await;
101 let repo = SqliteEventRepository::new(pool);
102
103 let target_date = NaiveDate::from_ymd_opt(2026, 3, 15).unwrap();
104 let target_datetime = target_date.and_hms_opt(10, 0, 0).unwrap();
105 let target_utc = chrono::DateTime::<Utc>::from_naive_utc_and_offset(target_datetime, Utc);
106
107 // Create event on target date
108 let on_date = NewEvent {
109 user_id: Some(user_id),
110 project_id: None,
111 title: "Event on target date".to_string(),
112 description: String::new(),
113 start_time: target_utc,
114 end_time: Some(target_utc + Duration::hours(2)),
115 location: None,
116 linked_task_id: None,
117 recurrence: Recurrence::None,
118 recurrence_rule: None,
119 contact_id: None,
120 block_type: None,
121 reminder_offsets_seconds: Vec::new(),
122 };
123 repo.create(user_id, on_date).await.expect("Failed to create");
124
125 // Create event on different date
126 let other_date = NewEvent {
127 user_id: Some(user_id),
128 project_id: None,
129 title: "Event on other date".to_string(),
130 description: String::new(),
131 start_time: target_utc + Duration::days(5),
132 end_time: None,
133 location: None,
134 linked_task_id: None,
135 recurrence: Recurrence::None,
136 recurrence_rule: None,
137 contact_id: None,
138 block_type: None,
139 reminder_offsets_seconds: Vec::new(),
140 };
141 repo.create(user_id, other_date).await.expect("Failed to create");
142
143 let events = repo.list_for_date(user_id, target_date).await.expect("Failed to list for date");
144 assert_eq!(events.len(), 1);
145 assert_eq!(events[0].title, "Event on target date");
146 }
147
148 #[tokio::test]
149 async fn test_update_event() {
150 let pool = common::setup_test_db().await;
151 let user_id = common::create_test_user(&pool).await;
152 let repo = SqliteEventRepository::new(pool);
153
154 let start = Utc::now() + Duration::hours(3);
155
156 let new_event = NewEvent {
157 user_id: Some(user_id),
158 project_id: None,
159 title: "Original Title".to_string(),
160 description: "Original description".to_string(),
161 start_time: start,
162 end_time: None,
163 location: None,
164 linked_task_id: None,
165 recurrence: Recurrence::None,
166 recurrence_rule: None,
167 contact_id: None,
168 block_type: None,
169 reminder_offsets_seconds: Vec::new(),
170 };
171
172 let created = repo.create(user_id, new_event).await.expect("Failed to create");
173
174 // Update the event
175 let update = goingson_core::UpdateEvent {
176 project_id: None,
177 title: "Updated Title".to_string(),
178 description: "Updated description".to_string(),
179 start_time: start + Duration::hours(1),
180 end_time: Some(start + Duration::hours(3)),
181 location: Some("New Location".to_string()),
182 linked_task_id: None,
183 recurrence: Recurrence::Weekly,
184 recurrence_rule: None,
185 contact_id: None,
186 block_type: None,
187 reminder_offsets_seconds: Vec::new(),
188 };
189
190 let updated = repo.update(created.id, user_id, update).await.expect("Failed to update");
191 assert!(updated.is_some());
192 let updated = updated.unwrap();
193 assert_eq!(updated.title, "Updated Title");
194 assert_eq!(updated.location, Some("New Location".to_string()));
195 assert_eq!(updated.recurrence, Recurrence::Weekly);
196 }
197
198 #[tokio::test]
199 async fn test_delete_event() {
200 let pool = common::setup_test_db().await;
201 let user_id = common::create_test_user(&pool).await;
202 let repo = SqliteEventRepository::new(pool);
203
204 let new_event = NewEvent {
205 user_id: Some(user_id),
206 project_id: None,
207 title: "To Delete".to_string(),
208 description: String::new(),
209 start_time: Utc::now() + Duration::hours(1),
210 end_time: None,
211 location: None,
212 linked_task_id: None,
213 recurrence: Recurrence::None,
214 recurrence_rule: None,
215 contact_id: None,
216 block_type: None,
217 reminder_offsets_seconds: Vec::new(),
218 };
219
220 let created = repo.create(user_id, new_event).await.expect("Failed to create");
221
222 let deleted = repo.delete(created.id, user_id).await.expect("Failed to delete");
223 assert!(deleted);
224
225 let fetched = repo.get_by_id(created.id, user_id).await.expect("Failed to get");
226 assert!(fetched.is_none());
227 }
228
229 #[tokio::test]
230 async fn test_event_with_linked_task() {
231 let pool = common::setup_test_db().await;
232 let user_id = common::create_test_user(&pool).await;
233 // Create a real task to link to (foreign key constraint)
234 let task_id = common::create_test_task(&pool, user_id).await;
235 let repo = SqliteEventRepository::new(pool);
236
237 let new_event = NewEvent {
238 user_id: Some(user_id),
239 project_id: None,
240 title: "Task time block".to_string(),
241 description: String::new(),
242 start_time: Utc::now() + Duration::hours(2),
243 end_time: Some(Utc::now() + Duration::hours(3)),
244 location: None,
245 linked_task_id: Some(task_id),
246 recurrence: Recurrence::None,
247 recurrence_rule: None,
248 contact_id: None,
249 block_type: None,
250 reminder_offsets_seconds: Vec::new(),
251 };
252
253 let created = repo.create(user_id, new_event).await.expect("Failed to create");
254 assert_eq!(created.linked_task_id, Some(task_id));
255
256 // Find event by linked task
257 let found = repo.get_by_linked_task(user_id, task_id).await.expect("Failed to get by task");
258 assert!(found.is_some());
259 assert_eq!(found.unwrap().id, created.id);
260
261 // Delete by linked task
262 let deleted = repo.delete_by_linked_task(user_id, task_id).await.expect("Failed to delete");
263 assert!(deleted);
264
265 let found = repo.get_by_linked_task(user_id, task_id).await.expect("Failed to get");
266 assert!(found.is_none());
267 }
268
269 #[tokio::test]
270 async fn test_event_ordering() {
271 let pool = common::setup_test_db().await;
272 let user_id = common::create_test_user(&pool).await;
273 let repo = SqliteEventRepository::new(pool);
274
275 // Create events in random order
276 for i in [3, 1, 2] {
277 let event = NewEvent {
278 user_id: Some(user_id),
279 project_id: None,
280 title: format!("Event {}", i),
281 description: String::new(),
282 start_time: Utc::now() + Duration::hours(i as i64),
283 end_time: None,
284 location: None,
285 linked_task_id: None,
286 recurrence: Recurrence::None,
287 recurrence_rule: None,
288 contact_id: None,
289 block_type: None,
290 reminder_offsets_seconds: Vec::new(),
291 };
292 repo.create(user_id, event).await.expect("Failed to create");
293 }
294
295 let events = repo.get_upcoming(user_id, 7).await.expect("Failed to list");
296 assert_eq!(events.len(), 3);
297
298 // Should be ordered by start_time ascending
299 assert!(events[0].start_time <= events[1].start_time);
300 assert!(events[1].start_time <= events[2].start_time);
301 }
302
303 #[tokio::test]
304 async fn test_list_all_events() {
305 let pool = common::setup_test_db().await;
306 let user_id = common::create_test_user(&pool).await;
307 let repo = SqliteEventRepository::new(pool);
308
309 // Create several events
310 for i in 0..5 {
311 let event = NewEvent {
312 user_id: Some(user_id),
313 project_id: None,
314 title: format!("Event {}", i),
315 description: String::new(),
316 start_time: Utc::now() + Duration::hours(i as i64),
317 end_time: None,
318 location: None,
319 linked_task_id: None,
320 recurrence: Recurrence::None,
321 recurrence_rule: None,
322 contact_id: None,
323 block_type: None,
324 reminder_offsets_seconds: Vec::new(),
325 };
326 repo.create(user_id, event).await.expect("Failed to create");
327 }
328
329 let all = repo.list_all(user_id).await.expect("Failed to list all");
330 assert_eq!(all.len(), 5);
331 }
332
333 #[tokio::test]
334 async fn test_event_snooze() {
335 let pool = common::setup_test_db().await;
336 let user_id = common::create_test_user(&pool).await;
337 let repo = SqliteEventRepository::new(pool);
338
339 let start = Utc::now() + Duration::hours(4);
340 let new_event = NewEvent {
341 user_id: Some(user_id),
342 project_id: None,
343 title: "Snoozable".to_string(),
344 description: String::new(),
345 start_time: start,
346 end_time: Some(start + Duration::hours(1)),
347 location: None,
348 linked_task_id: None,
349 recurrence: Recurrence::None,
350 recurrence_rule: None,
351 contact_id: None,
352 block_type: None,
353 reminder_offsets_seconds: Vec::new(),
354 };
355 let event = repo.create(user_id, new_event).await.expect("Failed to create event");
356 assert!(event.snoozed_until.is_none());
357
358 let until = Utc::now() + Duration::hours(2);
359 let snoozed = repo.snooze(event.id, user_id, until).await.expect("Failed to snooze");
360 assert!(snoozed.as_ref().unwrap().snoozed_until.is_some());
361 assert!(snoozed.unwrap().is_snoozed());
362
363 let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed");
364 assert_eq!(snoozed_list.len(), 1);
365
366 let unsnoozed = repo.unsnooze(event.id, user_id).await.expect("Failed to unsnooze");
367 assert!(unsnoozed.unwrap().snoozed_until.is_none());
368
369 let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed");
370 assert!(snoozed_list.is_empty());
371 }
372
373 #[tokio::test]
374 async fn test_event_snooze_past_time_excluded_from_list() {
375 let pool = common::setup_test_db().await;
376 let user_id = common::create_test_user(&pool).await;
377 let repo = SqliteEventRepository::new(pool);
378
379 let start = Utc::now() + Duration::hours(4);
380 let new_event = NewEvent {
381 user_id: Some(user_id),
382 project_id: None,
383 title: "Expired snooze".to_string(),
384 description: String::new(),
385 start_time: start,
386 end_time: None,
387 location: None,
388 linked_task_id: None,
389 recurrence: Recurrence::None,
390 recurrence_rule: None,
391 contact_id: None,
392 block_type: None,
393 reminder_offsets_seconds: Vec::new(),
394 };
395 let event = repo.create(user_id, new_event).await.expect("Failed to create event");
396
397 let past_until = Utc::now() - Duration::hours(1);
398 repo.snooze(event.id, user_id, past_until).await.expect("Failed to snooze");
399
400 // Expired snooze should not appear in list_snoozed
401 let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed");
402 assert!(snoozed_list.is_empty());
403 }
404
405 #[tokio::test]
406 async fn event_reminder_offsets_roundtrip() {
407 let pool = common::setup_test_db().await;
408 let user_id = common::create_test_user(&pool).await;
409 let repo = SqliteEventRepository::new(pool);
410
411 let start = Utc::now() + Duration::hours(2);
412 let new_event = NewEvent {
413 user_id: Some(user_id),
414 project_id: None,
415 title: "Reminded".to_string(),
416 description: String::new(),
417 start_time: start,
418 end_time: None,
419 location: None,
420 linked_task_id: None,
421 recurrence: Recurrence::None,
422 recurrence_rule: None,
423 contact_id: None,
424 block_type: None,
425 reminder_offsets_seconds: vec![0, 300, 900],
426 };
427 let created = repo.create(user_id, new_event).await.expect("create");
428 assert_eq!(created.reminder_offsets_seconds, vec![0, 300, 900]);
429
430 // Round-trip via get_by_id
431 let fetched = repo.get_by_id(created.id, user_id).await.expect("get").expect("present");
432 assert_eq!(fetched.reminder_offsets_seconds, vec![0, 300, 900]);
433
434 // Update clears the offsets
435 let update = goingson_core::UpdateEvent {
436 project_id: None,
437 title: "Reminded".to_string(),
438 description: String::new(),
439 start_time: start,
440 end_time: None,
441 location: None,
442 linked_task_id: None,
443 recurrence: Recurrence::None,
444 recurrence_rule: None,
445 contact_id: None,
446 block_type: None,
447 reminder_offsets_seconds: Vec::new(),
448 };
449 let updated = repo.update(created.id, user_id, update).await.expect("update").expect("present");
450 assert!(updated.reminder_offsets_seconds.is_empty());
451 }
452