//! Integration tests for SqliteEventRepository. mod common; use chrono::{Duration, NaiveDate, Utc}; use goingson_core::{EventRepository, NewEvent, Recurrence}; use goingson_db_sqlite::SqliteEventRepository; #[tokio::test] async fn test_create_and_get_event() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let start = Utc::now() + Duration::hours(2); let end = start + Duration::hours(1); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Team Meeting".to_string(), description: "Weekly standup".to_string(), start_time: start, end_time: Some(end), location: Some("Conference Room A".to_string()), linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let created = repo.create(user_id, new_event).await.expect("Failed to create event"); assert_eq!(created.title, "Team Meeting"); assert_eq!(created.location, Some("Conference Room A".to_string())); let fetched = repo.get_by_id(created.id, user_id).await.expect("Failed to get event"); assert!(fetched.is_some()); assert_eq!(fetched.unwrap().id, created.id); } #[tokio::test] async fn test_get_upcoming_events() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); // Create past event let past = NewEvent { user_id: Some(user_id), project_id: None, title: "Past Event".to_string(), description: String::new(), start_time: Utc::now() - Duration::days(2), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, past).await.expect("Failed to create"); // Create upcoming events for i in 1..=3 { let future = NewEvent { user_id: Some(user_id), project_id: None, title: format!("Future Event {}", i), description: String::new(), start_time: Utc::now() + Duration::days(i as i64), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, future).await.expect("Failed to create"); } // Get events in next 7 days let upcoming = repo.get_upcoming(user_id, 7).await.expect("Failed to get upcoming"); assert_eq!(upcoming.len(), 3); // All should be in the future for event in &upcoming { assert!(event.start_time > Utc::now()); } } #[tokio::test] async fn test_list_events_for_date() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let target_date = NaiveDate::from_ymd_opt(2026, 3, 15).unwrap(); let target_datetime = target_date.and_hms_opt(10, 0, 0).unwrap(); let target_utc = chrono::DateTime::::from_naive_utc_and_offset(target_datetime, Utc); // Create event on target date let on_date = NewEvent { user_id: Some(user_id), project_id: None, title: "Event on target date".to_string(), description: String::new(), start_time: target_utc, end_time: Some(target_utc + Duration::hours(2)), location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, on_date).await.expect("Failed to create"); // Create event on different date let other_date = NewEvent { user_id: Some(user_id), project_id: None, title: "Event on other date".to_string(), description: String::new(), start_time: target_utc + Duration::days(5), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, other_date).await.expect("Failed to create"); let events = repo.list_for_date(user_id, target_date).await.expect("Failed to list for date"); assert_eq!(events.len(), 1); assert_eq!(events[0].title, "Event on target date"); } #[tokio::test] async fn test_update_event() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let start = Utc::now() + Duration::hours(3); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Original Title".to_string(), description: "Original description".to_string(), start_time: start, end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let created = repo.create(user_id, new_event).await.expect("Failed to create"); // Update the event let update = goingson_core::UpdateEvent { project_id: None, title: "Updated Title".to_string(), description: "Updated description".to_string(), start_time: start + Duration::hours(1), end_time: Some(start + Duration::hours(3)), location: Some("New Location".to_string()), linked_task_id: None, recurrence: Recurrence::Weekly, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let updated = repo.update(created.id, user_id, update).await.expect("Failed to update"); assert!(updated.is_some()); let updated = updated.unwrap(); assert_eq!(updated.title, "Updated Title"); assert_eq!(updated.location, Some("New Location".to_string())); assert_eq!(updated.recurrence, Recurrence::Weekly); } #[tokio::test] async fn test_delete_event() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "To Delete".to_string(), description: String::new(), start_time: Utc::now() + Duration::hours(1), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let created = repo.create(user_id, new_event).await.expect("Failed to create"); let deleted = repo.delete(created.id, user_id).await.expect("Failed to delete"); assert!(deleted); let fetched = repo.get_by_id(created.id, user_id).await.expect("Failed to get"); assert!(fetched.is_none()); } #[tokio::test] async fn test_event_with_linked_task() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; // Create a real task to link to (foreign key constraint) let task_id = common::create_test_task(&pool, user_id).await; let repo = SqliteEventRepository::new(pool); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Task time block".to_string(), description: String::new(), start_time: Utc::now() + Duration::hours(2), end_time: Some(Utc::now() + Duration::hours(3)), location: None, linked_task_id: Some(task_id), recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let created = repo.create(user_id, new_event).await.expect("Failed to create"); assert_eq!(created.linked_task_id, Some(task_id)); // Find event by linked task let found = repo.get_by_linked_task(user_id, task_id).await.expect("Failed to get by task"); assert!(found.is_some()); assert_eq!(found.unwrap().id, created.id); // Delete by linked task let deleted = repo.delete_by_linked_task(user_id, task_id).await.expect("Failed to delete"); assert!(deleted); let found = repo.get_by_linked_task(user_id, task_id).await.expect("Failed to get"); assert!(found.is_none()); } #[tokio::test] async fn test_event_ordering() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); // Create events in random order for i in [3, 1, 2] { let event = NewEvent { user_id: Some(user_id), project_id: None, title: format!("Event {}", i), description: String::new(), start_time: Utc::now() + Duration::hours(i as i64), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, event).await.expect("Failed to create"); } let events = repo.get_upcoming(user_id, 7).await.expect("Failed to list"); assert_eq!(events.len(), 3); // Should be ordered by start_time ascending assert!(events[0].start_time <= events[1].start_time); assert!(events[1].start_time <= events[2].start_time); } #[tokio::test] async fn test_list_all_events() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); // Create several events for i in 0..5 { let event = NewEvent { user_id: Some(user_id), project_id: None, title: format!("Event {}", i), description: String::new(), start_time: Utc::now() + Duration::hours(i as i64), end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; repo.create(user_id, event).await.expect("Failed to create"); } let all = repo.list_all(user_id).await.expect("Failed to list all"); assert_eq!(all.len(), 5); } #[tokio::test] async fn test_event_snooze() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let start = Utc::now() + Duration::hours(4); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Snoozable".to_string(), description: String::new(), start_time: start, end_time: Some(start + Duration::hours(1)), location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let event = repo.create(user_id, new_event).await.expect("Failed to create event"); assert!(event.snoozed_until.is_none()); let until = Utc::now() + Duration::hours(2); let snoozed = repo.snooze(event.id, user_id, until).await.expect("Failed to snooze"); assert!(snoozed.as_ref().unwrap().snoozed_until.is_some()); assert!(snoozed.unwrap().is_snoozed()); let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed"); assert_eq!(snoozed_list.len(), 1); let unsnoozed = repo.unsnooze(event.id, user_id).await.expect("Failed to unsnooze"); assert!(unsnoozed.unwrap().snoozed_until.is_none()); let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed"); assert!(snoozed_list.is_empty()); } #[tokio::test] async fn test_event_snooze_past_time_excluded_from_list() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let start = Utc::now() + Duration::hours(4); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Expired snooze".to_string(), description: String::new(), start_time: start, end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let event = repo.create(user_id, new_event).await.expect("Failed to create event"); let past_until = Utc::now() - Duration::hours(1); repo.snooze(event.id, user_id, past_until).await.expect("Failed to snooze"); // Expired snooze should not appear in list_snoozed let snoozed_list = repo.list_snoozed(user_id).await.expect("Failed to list snoozed"); assert!(snoozed_list.is_empty()); } #[tokio::test] async fn event_reminder_offsets_roundtrip() { let pool = common::setup_test_db().await; let user_id = common::create_test_user(&pool).await; let repo = SqliteEventRepository::new(pool); let start = Utc::now() + Duration::hours(2); let new_event = NewEvent { user_id: Some(user_id), project_id: None, title: "Reminded".to_string(), description: String::new(), start_time: start, end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: vec![0, 300, 900], }; let created = repo.create(user_id, new_event).await.expect("create"); assert_eq!(created.reminder_offsets_seconds, vec![0, 300, 900]); // Round-trip via get_by_id let fetched = repo.get_by_id(created.id, user_id).await.expect("get").expect("present"); assert_eq!(fetched.reminder_offsets_seconds, vec![0, 300, 900]); // Update clears the offsets let update = goingson_core::UpdateEvent { project_id: None, title: "Reminded".to_string(), description: String::new(), start_time: start, end_time: None, location: None, linked_task_id: None, recurrence: Recurrence::None, recurrence_rule: None, contact_id: None, block_type: None, reminder_offsets_seconds: Vec::new(), }; let updated = repo.update(created.id, user_id, update).await.expect("update").expect("present"); assert!(updated.reminder_offsets_seconds.is_empty()); }