feat: Complete import functionality with RRULE fixes and comprehensive testing
- Fix RRULE BYDAY filtering for daily frequency events (Tether sync weekdays only) - Fix timezone transfer in recurring event expansion - Add comprehensive timezone-aware iCal generation - Add extensive test suite for recurrence and timezone functionality - Update dependencies and configuration examples - Implement cleanup logic for orphaned events - Add detailed import plan documentation This completes the core import functionality with proper timezone handling, RRULE parsing, and duplicate prevention mechanisms.
This commit is contained in:
parent
932b6ae463
commit
640ae119d1
14 changed files with 3057 additions and 182 deletions
|
|
@ -225,6 +225,277 @@ mod filter_tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue