- Rust-based CLI tool for Zoho to Nextcloud calendar sync - Selective calendar import from Zoho to single Nextcloud calendar - Timezone-aware event handling for next-week synchronization - Comprehensive configuration system with TOML support - CLI interface with debug, list, and sync operations - Complete documentation and example configurations
285 lines
9.2 KiB
Rust
285 lines
9.2 KiB
Rust
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 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(())
|
|
}
|
|
}
|