Major refactoring to add robust event listing functionality with extensive debugging: - Add CalDAV event listing with timezone support and proper XML parsing - Implement comprehensive debug mode with request/response logging - Add event filtering capabilities with date range and timezone conversion - Refactor configuration to use structured TOML for better organization - Add proper timezone handling with timezone database integration - Improve error handling and logging throughout the application - Add comprehensive test suite for event listing and filtering - Create detailed testing documentation and usage examples This enables debugging of CalDAV server connections and event retrieval with proper timezone handling and detailed logging for troubleshooting.
287 lines
9.3 KiB
Rust
287 lines
9.3 KiB
Rust
use caldav_sync::{Config, CalDavResult};
|
|
use chrono::Utc;
|
|
|
|
#[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;
|
|
|
|
#[test]
|
|
fn test_error_retryable() {
|
|
// Create a simple network error test - skip the reqwest::Error creation
|
|
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());
|
|
|
|
// Just test that is_retryable works for different error types
|
|
assert!(!CalDavError::Authentication("test".to_string()).is_retryable());
|
|
assert!(!CalDavError::Config("test".to_string()).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;
|
|
use caldav_sync::CalDavResult;
|
|
use chrono::{DateTime, Utc};
|
|
|
|
#[test]
|
|
fn test_timezone_handler_creation() -> CalDavResult<()> {
|
|
let handler = TimezoneHandler::new(Some("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 mut 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,
|
|
EventStatusFilter, FilterBuilder
|
|
};
|
|
use caldav_sync::event::{Event, EventStatus};
|
|
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.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()])
|
|
.build();
|
|
|
|
let event = Event::new("Team Meeting".to_string(), Utc::now(), Utc::now());
|
|
assert!(filter.matches_event(&event)); // Matches condition
|
|
}
|
|
}
|
|
|
|
#[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(())
|
|
}
|
|
}
|