use caldav_sync::{Config, CalDavResult}; #[cfg(test)] mod config_tests { use super::*; #[test] fn test_default_config() -> CalDavResult<()> { let config = Config::default(); assert_eq!(config.server.url, "https://caldav.example.com"); assert_eq!(config.calendar.name, "calendar"); assert_eq!(config.sync.interval, 300); config.validate()?; Ok(()) } #[test] fn test_config_validation() -> CalDavResult<()> { let mut config = Config::default(); // Should fail with empty credentials assert!(config.validate().is_err()); config.server.username = "test_user".to_string(); config.server.password = "test_pass".to_string(); // Should succeed now assert!(config.validate().is_ok()); Ok(()) } } #[cfg(test)] mod error_tests { use caldav_sync::{CalDavError, CalDavResult}; #[test] fn test_error_retryable() { let network_error = CalDavError::Network( reqwest::Error::from(std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "test")) ); assert!(network_error.is_retryable()); let auth_error = CalDavError::Authentication("Invalid credentials".to_string()); assert!(!auth_error.is_retryable()); let config_error = CalDavError::Config("Missing URL".to_string()); assert!(!config_error.is_retryable()); } #[test] fn test_error_classification() { let auth_error = CalDavError::Authentication("Invalid".to_string()); assert!(auth_error.is_auth_error()); let config_error = CalDavError::Config("Invalid".to_string()); assert!(config_error.is_config_error()); } } #[cfg(test)] mod event_tests { use caldav_sync::event::{Event, EventStatus, EventType}; use chrono::{DateTime, Utc, NaiveDate}; #[test] fn test_event_creation() { let start = Utc::now(); let end = start + chrono::Duration::hours(1); let event = Event::new("Test Event".to_string(), start, end); assert_eq!(event.summary, "Test Event"); assert_eq!(event.start, start); assert_eq!(event.end, end); assert!(!event.all_day); assert_eq!(event.status, EventStatus::Confirmed); assert_eq!(event.event_type, EventType::Public); } #[test] fn test_all_day_event() { let date = NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(); let event = Event::new_all_day("Christmas".to_string(), date); assert_eq!(event.summary, "Christmas"); assert!(event.all_day); assert!(event.occurs_on(date)); } #[test] fn test_event_to_ical() -> caldav_sync::CalDavResult<()> { let event = Event::new( "Meeting".to_string(), DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T100000", "%Y%m%dT%H%M%S").unwrap(), Utc ), DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T110000", "%Y%m%dT%H%M%S").unwrap(), Utc ), ); let ical = event.to_ical()?; assert!(ical.contains("SUMMARY:Meeting")); assert!(ical.contains("DTSTART:20231225T100000Z")); assert!(ical.contains("DTEND:20231225T110000Z")); assert!(ical.contains("BEGIN:VCALENDAR")); assert!(ical.contains("END:VCALENDAR")); Ok(()) } } #[cfg(test)] mod timezone_tests { use caldav_sync::timezone::TimezoneHandler; #[test] fn test_timezone_handler_creation() -> CalDavResult<()> { let handler = TimezoneHandler::new("UTC")?; assert_eq!(handler.default_timezone(), "UTC"); Ok(()) } #[test] fn test_timezone_validation() { assert!(TimezoneHandler::validate_timezone("UTC")); assert!(TimezoneHandler::validate_timezone("America/New_York")); assert!(TimezoneHandler::validate_timezone("Europe/London")); assert!(!TimezoneHandler::validate_timezone("Invalid/Timezone")); } #[test] fn test_ical_formatting() -> CalDavResult<()> { let handler = TimezoneHandler::default(); let dt = DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T100000", "%Y%m%dT%H%M%S").unwrap(), Utc ); let ical_utc = handler.format_ical_datetime(dt, false)?; assert_eq!(ical_utc, "20231225T100000Z"); let ical_date = handler.format_ical_date(dt); assert_eq!(ical_date, "20231225"); Ok(()) } } #[cfg(test)] mod filter_tests { use caldav_sync::calendar_filter::{ CalendarFilter, FilterRule, DateRangeFilter, KeywordFilter, EventTypeFilter, EventStatusFilter, FilterBuilder }; use caldav_sync::event::{Event, EventStatus, EventType}; use chrono::{DateTime, Utc}; #[test] fn test_date_range_filter() { let start = DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T000000", "%Y%m%dT%H%M%S").unwrap(), Utc ); let end = DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T235959", "%Y%m%dT%H%M%S").unwrap(), Utc ); let filter = DateRangeFilter::new(start, end); let event_start = DateTime::from_naive_utc_and_offset( chrono::NaiveDateTime::parse_from_str("20231225T100000", "%Y%m%dT%H%M%S").unwrap(), Utc ); let event = Event::new("Test".to_string(), event_start, event_start + chrono::Duration::hours(1)); assert!(filter.matches_event(&event)); let event_outside = Event::new( "Test".to_string(), start - chrono::Duration::days(1), start - chrono::Duration::hours(23), ); assert!(!filter_outside.matches_event(&event_outside)); } #[test] fn test_keyword_filter() { let filter = KeywordFilter::new(vec!["meeting".to_string(), "important".to_string()], false); let event1 = Event::new("Team Meeting".to_string(), Utc::now(), Utc::now()); assert!(filter.matches_event(&event1)); let event2 = Event::new("Lunch".to_string(), Utc::now(), Utc::now()); assert!(!filter.matches_event(&event2)); } #[test] fn test_calendar_filter() { let mut filter = CalendarFilter::new(true); // OR logic filter.add_rule(FilterRule::Keywords(KeywordFilter::new(vec!["meeting".to_string()], false))); filter.add_rule(FilterRule::EventStatus(EventStatusFilter::new(vec![EventStatus::Cancelled]))); let event1 = Event::new("Team Meeting".to_string(), Utc::now(), Utc::now()); assert!(filter.matches_event(&event1)); // Matches keyword let mut event2 = Event::new("Holiday".to_string(), Utc::now(), Utc::now()); event2.status = EventStatus::Cancelled; assert!(filter.matches_event(&event2)); // Matches status let event3 = Event::new("Lunch".to_string(), Utc::now(), Utc::now()); assert!(!filter.matches_event(&event3)); // Matches neither } #[test] fn test_filter_builder() { let filter = FilterBuilder::new() .match_any(false) // AND logic .keywords(vec!["meeting".to_string()]) .event_types(vec![EventType::Public]) .build(); let event = Event::new("Team Meeting".to_string(), Utc::now(), Utc::now()); assert!(filter.matches_event(&event)); // Matches both conditions } } #[cfg(test)] mod live_caldav_tests { use caldav_sync::Config; use caldav_sync::minicaldav_client::RealCalDavClient; use caldav_sync::event::Event; use chrono::{DateTime, Utc, Duration}; use tokio; use std::path::PathBuf; /// Test basic CRUD operations on the import calendar using the test configuration #[tokio::test] async fn test_create_update_delete_event() -> Result<(), Box> { println!("๐Ÿงช Starting CRUD test with import calendar..."); // Load test configuration let config_path = PathBuf::from("config-test-import.toml"); let config = Config::from_file(&config_path)?; // Validate configuration config.validate()?; // Create CalDAV client for target server (Nextcloud) let import_config = config.get_import_config().ok_or("No import configuration found")?; let target_client = RealCalDavClient::new( &import_config.target_server.url, &import_config.target_server.username, &import_config.target_server.password, ).await?; // Build target calendar URL let target_calendar_url = format!("{}/", import_config.target_server.url.trim_end_matches('/')); // Validate target calendar let is_valid = target_client.validate_target_calendar(&target_calendar_url).await?; assert!(is_valid, "Target calendar should be accessible"); println!("โœ… Target calendar is accessible"); // Create test event for today let now = Utc::now(); let today_start = now.date_naive().and_hms_opt(10, 0, 0).unwrap().and_utc(); let today_end = today_start + Duration::hours(1); let test_uid = format!("test-event-{}", now.timestamp()); let mut test_event = Event::new( format!("Test Event {}", test_uid), today_start, today_end, ); test_event.uid = test_uid.clone(); test_event.description = Some("This is a test event for CRUD operations".to_string()); test_event.location = Some("Test Location".to_string()); println!("๐Ÿ“ Creating test event: {}", test_event.summary); // Convert event to iCalendar format let ical_data = test_event.to_ical()?; // Test 1: Create event let create_result = target_client.put_event( &target_calendar_url, &test_uid, &ical_data, None // No ETag for creation ).await; match create_result { Ok(_) => println!("โœ… Event created successfully"), Err(e) => { println!("โŒ Failed to create event: {}", e); return Err(e.into()); } } // Wait a moment to ensure the event is processed tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Test 2: Verify event exists println!("๐Ÿ” Verifying event exists..."); let etag_result = target_client.get_event_etag(&target_calendar_url, &test_uid).await; let original_etag = match etag_result { Ok(Some(etag)) => { println!("โœ… Event verified, ETag: {}", etag); etag } Ok(None) => { println!("โŒ Event not found after creation"); return Err("Event not found after creation".into()); } Err(e) => { println!("โŒ Failed to verify event: {}", e); return Err(e.into()); } } // Test 3: Update event (change date to tomorrow) println!("๐Ÿ“ Updating event for tomorrow..."); let tomorrow_start = today_start + Duration::days(1); let tomorrow_end = tomorrow_start + Duration::hours(1); test_event.start = tomorrow_start; test_event.end = tomorrow_end; test_event.summary = format!("Test Event {} (Updated for Tomorrow)", test_uid); test_event.description = Some("This event has been updated to tomorrow".to_string()); test_event.sequence += 1; // Increment sequence for update // Convert updated event to iCalendar format let updated_ical_data = test_event.to_ical()?; let update_result = target_client.put_event( &target_calendar_url, &test_uid, &updated_ical_data, Some(&original_etag) // Use ETag for update ).await; match update_result { Ok(_) => println!("โœ… Event updated successfully"), Err(e) => { println!("โŒ Failed to update event: {}", e); return Err(e.into()); } } // Wait a moment to ensure the update is processed tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Test 4: Verify event was updated (ETag should change) println!("๐Ÿ” Verifying event update..."); let new_etag_result = target_client.get_event_etag(&target_calendar_url, &test_uid).await; match new_etag_result { Ok(Some(new_etag)) => { if new_etag != original_etag { println!("โœ… Event updated, new ETag: {}", new_etag); } else { println!("โš ๏ธ Event ETag didn't change after update"); } } Ok(None) => { println!("โŒ Event not found after update"); return Err("Event not found after update".into()); } Err(e) => { println!("โŒ Failed to verify updated event: {}", e); return Err(e.into()); } } // Test 5: Delete event println!("๐Ÿ—‘๏ธ Deleting event..."); let delete_result = target_client.delete_event( &target_calendar_url, &test_uid, None // No ETag for deletion (let server handle it) ).await; match delete_result { Ok(_) => println!("โœ… Event deleted successfully"), Err(e) => { println!("โŒ Failed to delete event: {}", e); return Err(e.into()); } } // Wait a moment to ensure the deletion is processed tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Test 6: Verify event was deleted println!("๐Ÿ” Verifying event deletion..."); let final_check = target_client.get_event_etag(&target_calendar_url, &test_uid).await; match final_check { Ok(None) => println!("โœ… Event successfully deleted"), Ok(Some(etag)) => { println!("โŒ Event still exists after deletion, ETag: {}", etag); return Err("Event still exists after deletion".into()); } Err(e) => { println!("โŒ Failed to verify deletion: {}", e); return Err(e.into()); } } println!("๐ŸŽ‰ All CRUD operations completed successfully!"); Ok(()) } /// Test HTTP error handling by attempting to delete a non-existent event #[tokio::test] async fn test_delete_nonexistent_event() -> Result<(), Box> { println!("๐Ÿงช Testing deletion of non-existent event..."); // Load test configuration let config_path = PathBuf::from("config-test-import.toml"); let config = Config::from_file(&config_path)?; // Create CalDAV client for target server let import_config = config.get_import_config().ok_or("No import configuration found")?; let target_client = RealCalDavClient::new( &import_config.target_server.url, &import_config.target_server.username, &import_config.target_server.password, ).await?; // Build target calendar URL let target_calendar_url = format!("{}/", import_config.target_server.url.trim_end_matches('/')); // Try to delete a non-existent event let fake_uid = "non-existent-event-12345"; println!("๐Ÿ—‘๏ธ Testing deletion of non-existent event: {}", fake_uid); let delete_result = target_client.delete_event( &target_calendar_url, fake_uid, None ).await; match delete_result { Ok(_) => { println!("โœ… Non-existent event deletion handled gracefully (idempotent)"); Ok(()) } Err(e) => { println!("โŒ Failed to handle non-existent event deletion gracefully: {}", e); Err(e.into()) } } } /// Test event existence checking #[tokio::test] async fn test_event_existence_check() -> Result<(), Box> { println!("๐Ÿงช Testing event existence check..."); // Load test configuration let config_path = PathBuf::from("config-test-import.toml"); let config = Config::from_file(&config_path)?; // Create CalDAV client for target server let import_config = config.get_import_config().ok_or("No import configuration found")?; let target_client = RealCalDavClient::new( &import_config.target_server.url, &import_config.target_server.username, &import_config.target_server.password, ).await?; // Build target calendar URL let target_calendar_url = format!("{}/", import_config.target_server.url.trim_end_matches('/')); // Test non-existent event let fake_uid = "non-existent-event-67890"; let fake_event_url = format!("{}{}.ics", target_calendar_url, fake_uid); println!("๐Ÿ” Testing existence check for non-existent event: {}", fake_uid); let existence_result = target_client.check_event_exists(&fake_event_url).await; match existence_result { Ok(_) => { println!("โŒ Non-existent event reported as existing"); Err("Non-existent event reported as existing".into()) } Err(e) => { println!("โœ… Non-existent event correctly reported as missing: {}", e); Ok(()) } } } } #[cfg(test)] mod integration_tests { use super::*; #[test] fn test_library_initialization() -> CalDavResult<()> { caldav_sync::init()?; Ok(()) } #[test] fn test_version() { assert!(!caldav_sync::VERSION.is_empty()); } #[test] fn test_full_workflow() -> CalDavResult<()> { // Initialize library caldav_sync::init()?; // Create configuration let config = Config::default(); // Validate configuration config.validate()?; // Create some test events let event1 = caldav_sync::event::Event::new( "Test Meeting".to_string(), Utc::now(), Utc::now() + chrono::Duration::hours(1), ); let event2 = caldav_sync::event::Event::new_all_day( "Test Holiday".to_string(), chrono::NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(), ); // Test event serialization let ical1 = event1.to_ical()?; let ical2 = event2.to_ical()?; assert!(!ical1.is_empty()); assert!(!ical2.is_empty()); assert!(ical1.contains("SUMMARY:Test Meeting")); assert!(ical2.contains("SUMMARY:Test Holiday")); // Test filtering let filter = caldav_sync::calendar_filter::FilterBuilder::new() .keywords(vec!["test".to_string()]) .build(); assert!(filter.matches_event(&event1)); assert!(filter.matches_event(&event2)); Ok(()) } }