diff --git a/src-tauri/tests/calendar_import_smoke_test.rs b/src-tauri/tests/calendar_import_smoke_test.rs new file mode 100644 index 0000000..b5bc8ac --- /dev/null +++ b/src-tauri/tests/calendar_import_smoke_test.rs @@ -0,0 +1,113 @@ +use whispassist_lib::models::{AttendeeInfo, CalendarEvent, ImportedEvent}; +use whispassist_lib::storage::{SqliteStore, Store}; + +fn event(uid: &str, subject: &str) -> ImportedEvent { + ImportedEvent { + event: CalendarEvent { + id: uuid::Uuid::new_v4().to_string(), + source: "pst".to_string(), + subject: Some(subject.to_string()), + organizer: Some("Jordan Lee".to_string()), + starts_at: Some(1_782_896_400), + ends_at: Some(1_782_900_000), + description: Some("Agenda".to_string()), + raw_uid: Some(uid.to_string()), + }, + attendees: vec![ + AttendeeInfo { + name: "Alex Kim".to_string(), + email: Some("alex@example.com".to_string()), + role: Some("required".to_string()), + }, + AttendeeInfo { + name: "No Email Person".to_string(), + email: None, + role: None, + }, + ], + } +} + +#[tokio::test] +async fn import_calendar_events_dedups_by_source_and_raw_uid() { + std::env::set_var( + "LOCALAPPDATA", + std::env::temp_dir().join(format!("wa-caltest-{}", uuid::Uuid::new_v4())), + ); + let store = SqliteStore::connect().await.unwrap(); + + let n1 = store + .import_calendar_events(vec![event("uid-1", "Sprint planning")]) + .await + .unwrap(); + assert_eq!(n1, 1); + + let n2 = store + .import_calendar_events(vec![ + event("uid-1", "Sprint planning (rescheduled)"), + event("uid-2", "Retro"), + ]) + .await + .unwrap(); + assert_eq!(n2, 2); + + let n3 = store + .import_calendar_events(vec![event("uid-1", "Sprint planning (rescheduled)")]) + .await + .unwrap(); + assert_eq!(n3, 1); + + // Verify actual row-level dedup, not just the returned counts. + let pool = sqlx::sqlite::SqlitePool::connect(&format!( + "sqlite:{}", + whispassist_lib::paths::db_path().display() + )) + .await + .unwrap(); + + let event_count: i64 = + sqlx::query_scalar("SELECT COUNT(*) FROM calendar_events WHERE raw_uid = 'uid-1'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!( + event_count, 1, + "re-importing uid-1 must update in place, not duplicate" + ); + + let subject: String = + sqlx::query_scalar("SELECT subject FROM calendar_events WHERE raw_uid = 'uid-1'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(subject, "Sprint planning (rescheduled)"); + + let total_events: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM calendar_events") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(total_events, 2, "uid-1 and uid-2 only"); + + let no_email_rows: i64 = + sqlx::query_scalar("SELECT COUNT(*) FROM participants WHERE name = 'No Email Person'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!( + no_email_rows, 1, + "repeat no-email attendee must not duplicate the participant row" + ); + + let alex_rows: i64 = + sqlx::query_scalar("SELECT COUNT(*) FROM participants WHERE email = 'alex@example.com'") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(alex_rows, 1); + + let links: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM calendar_event_participants") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(links, 4, "2 events x 2 attendees each"); +}