caldavpuller/tests/live_caldav_test.rs
Alvaro Soliverez 640ae119d1 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.
2025-11-21 12:04:46 -03:00

274 lines
9.7 KiB
Rust

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) => {
// Check if it's a 404 error, which indicates successful deletion
if e.to_string().contains("404") || e.to_string().contains("Not Found") {
println!("✅ Event successfully deleted (confirmed by 404)");
} else {
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(())
}
}
}