From ce0ce434183cf44aeeae982bc5b4ad8bfab83280 Mon Sep 17 00:00:00 2001
From: Lennart <18233294+lennart-k@users.noreply.github.com>
Date: Sun, 10 Aug 2025 13:14:45 +0200
Subject: [PATCH] some preparation for better testing
---
Cargo.lock | 1 +
crates/caldav/Cargo.toml | 1 +
crates/caldav/src/calendar/mod.rs | 3 +
crates/caldav/src/calendar/resource.rs | 3 +-
.../src/calendar/test_files/propfind.outputs | 222 ++++++++++++++++++
.../test_files/propfind.principals.json | 11 +
.../src/calendar/test_files/propfind.requests | 6 +
.../test_files/propfind.resources.json | 42 ++++
crates/caldav/src/calendar/tests.rs | 47 ++++
crates/caldav/src/principal/tests.rs | 42 +++-
crates/ical/src/icalendar/object.rs | 3 +-
crates/store/src/calendar.rs | 4 +-
12 files changed, 378 insertions(+), 7 deletions(-)
create mode 100644 crates/caldav/src/calendar/test_files/propfind.outputs
create mode 100644 crates/caldav/src/calendar/test_files/propfind.principals.json
create mode 100644 crates/caldav/src/calendar/test_files/propfind.requests
create mode 100644 crates/caldav/src/calendar/test_files/propfind.resources.json
create mode 100644 crates/caldav/src/calendar/tests.rs
diff --git a/Cargo.lock b/Cargo.lock
index 3b8adf6..d4b0ee6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3093,6 +3093,7 @@ dependencies = [
"rustical_store_sqlite",
"rustical_xml",
"serde",
+ "serde_json",
"sha2",
"strum",
"strum_macros",
diff --git a/crates/caldav/Cargo.toml b/crates/caldav/Cargo.toml
index 2270f5a..2903282 100644
--- a/crates/caldav/Cargo.toml
+++ b/crates/caldav/Cargo.toml
@@ -11,6 +11,7 @@ publish = false
rustical_store_sqlite = { workspace = true, features = ["test"] }
rstest.workspace = true
async-std.workspace = true
+serde_json.workspace = true
[dependencies]
axum.workspace = true
diff --git a/crates/caldav/src/calendar/mod.rs b/crates/caldav/src/calendar/mod.rs
index 2d092d8..d8df251 100644
--- a/crates/caldav/src/calendar/mod.rs
+++ b/crates/caldav/src/calendar/mod.rs
@@ -4,3 +4,6 @@ pub mod resource;
mod service;
pub use service::CalendarResourceService;
+
+#[cfg(test)]
+pub mod tests;
diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs
index 37b7c57..9afdbe0 100644
--- a/crates/caldav/src/calendar/resource.rs
+++ b/crates/caldav/src/calendar/resource.rs
@@ -16,6 +16,7 @@ use rustical_store::Calendar;
use rustical_store::auth::Principal;
use rustical_xml::{EnumVariants, PropName};
use rustical_xml::{XmlDeserialize, XmlSerialize};
+use serde::Deserialize;
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)]
#[xml(unit_variants_ident = "CalendarPropName")]
@@ -62,7 +63,7 @@ pub enum CalendarPropWrapper {
Common(CommonPropertiesProp),
}
-#[derive(Clone, Debug, From, Into)]
+#[derive(Clone, Debug, From, Into, Deserialize)]
pub struct CalendarResource {
pub cal: Calendar,
pub read_only: bool,
diff --git a/crates/caldav/src/calendar/test_files/propfind.outputs b/crates/caldav/src/calendar/test_files/propfind.outputs
new file mode 100644
index 0000000..38f4e04
--- /dev/null
+++ b/crates/caldav/src/calendar/test_files/propfind.outputs
@@ -0,0 +1,222 @@
+
+
+ /caldav/principal/user/calendar/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+
+
+
+ /caldav/principal/user/calendar/
+
+
+ BEGIN:VCALENDAR
+PRODID:-//github.com/lennart-k/vzic-rs//RustiCal Calendar server//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+LAST-MODIFIED:20250723T190331Z
+X-LIC-LOCATION:Europe/Berlin
+X-PROLEPTIC-TZNAME:LMT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+005328
+TZOFFSETTO:+0100
+DTSTART:18930401T000000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19160430T230000
+RDATE:19400401T020000
+RDATE:19430329T020000
+RDATE:19460414T020000
+RDATE:19470406T030000
+RDATE:19480418T020000
+RDATE:19490410T020000
+RDATE:19800406T020000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19161001T010000
+RDATE:19421102T030000
+RDATE:19431004T030000
+RDATE:19441002T030000
+RDATE:19451118T030000
+RDATE:19461007T030000
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19170416T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3MO;UNTIL=19180415T010000Z
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19170917T030000
+RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3MO;UNTIL=19180916T010000Z
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19440403T020000
+RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1MO;UNTIL=19450402T010000Z
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZNAME:CEMT
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0300
+DTSTART:19450524T020000
+RDATE:19470511T030000
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0300
+TZOFFSETTO:+0200
+DTSTART:19450924T030000
+RDATE:19470629T030000
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0100
+DTSTART:19460101T000000
+RDATE:19800101T000000
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19471005T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19491002T010000Z
+END:STANDARD
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19800928T030000
+RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU;UNTIL=19950924T010000Z
+END:STANDARD
+BEGIN:DAYLIGHT
+TZNAME:CEST
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:19810329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZNAME:CET
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+DTSTART:19961027T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+
+
+ https://www.iana.org/time-zones
+
+ Europe/Berlin
+ 0
+
+
+
+
+
+
+
+ 10000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -2621430101T000000Z
+ +2621421231T235959Z
+ github.com/lennart-k/rustical/ns/12
+ github.com/lennart-k/rustical/ns/12
+
+
+
+ b28b41e9-8801-4fc5-ae29-8efb5fadeb36
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+ Calendar
+
+ /caldav/principal/user/
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /caldav/principal/user/
+
+
+ HTTP/1.1 200 OK
+
+
diff --git a/crates/caldav/src/calendar/test_files/propfind.principals.json b/crates/caldav/src/calendar/test_files/propfind.principals.json
new file mode 100644
index 0000000..ec1270d
--- /dev/null
+++ b/crates/caldav/src/calendar/test_files/propfind.principals.json
@@ -0,0 +1,11 @@
+[
+ {
+ "id": "user",
+ "displayname": null,
+ "principal_type": "individual",
+ "password": null,
+ "memberships": [
+ "group"
+ ]
+ }
+]
diff --git a/crates/caldav/src/calendar/test_files/propfind.requests b/crates/caldav/src/calendar/test_files/propfind.requests
new file mode 100644
index 0000000..846c969
--- /dev/null
+++ b/crates/caldav/src/calendar/test_files/propfind.requests
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/crates/caldav/src/calendar/test_files/propfind.resources.json b/crates/caldav/src/calendar/test_files/propfind.resources.json
new file mode 100644
index 0000000..e042f61
--- /dev/null
+++ b/crates/caldav/src/calendar/test_files/propfind.resources.json
@@ -0,0 +1,42 @@
+[
+ {
+ "cal": {
+ "principal": "user",
+ "id": "calendar",
+ "displayname": "Calendar",
+ "order": 0,
+ "description": null,
+ "color": null,
+ "timezone_id": "Europe/Berlin",
+ "deleted_at": null,
+ "synctoken": 12,
+ "subscription_url": null,
+ "push_topic": "b28b41e9-8801-4fc5-ae29-8efb5fadeb36",
+ "components": [
+ "VEVENT",
+ "VTODO"
+ ]
+ },
+ "read_only": true
+ },
+ {
+ "cal": {
+ "principal": "user",
+ "id": "calendar",
+ "displayname": "Calendar",
+ "order": 0,
+ "description": null,
+ "color": null,
+ "timezone_id": "Europe/Berlin",
+ "deleted_at": null,
+ "synctoken": 12,
+ "subscription_url": null,
+ "push_topic": "b28b41e9-8801-4fc5-ae29-8efb5fadeb36",
+ "components": [
+ "VEVENT",
+ "VTODO"
+ ]
+ },
+ "read_only": true
+ }
+]
diff --git a/crates/caldav/src/calendar/tests.rs b/crates/caldav/src/calendar/tests.rs
new file mode 100644
index 0000000..7bf6a2a
--- /dev/null
+++ b/crates/caldav/src/calendar/tests.rs
@@ -0,0 +1,47 @@
+use crate::{CalDavPrincipalUri, calendar::resource::CalendarResource};
+use rustical_dav::resource::Resource;
+use rustical_store::auth::Principal;
+use rustical_xml::XmlSerializeRoot;
+use serde_json::from_str;
+
+// #[tokio::test]
+async fn test_propfind() {
+ let requests: Vec<_> = include_str!("./test_files/propfind.requests")
+ .trim()
+ .split("\n\n")
+ .collect();
+ let principals: Vec =
+ from_str(include_str!("./test_files/propfind.principals.json")).unwrap();
+ let resources: Vec =
+ from_str(include_str!("./test_files/propfind.resources.json")).unwrap();
+ let outputs: Vec<_> = include_str!("./test_files/propfind.outputs")
+ .trim()
+ .split("\n\n")
+ .collect();
+
+ for principal in principals {
+ for ((request, resource), &expected_output) in requests.iter().zip(&resources).zip(&outputs)
+ {
+ let propfind = CalendarResource::parse_propfind(request).unwrap();
+
+ let response = resource
+ .propfind(
+ &format!("/caldav/principal/{}/{}", principal.id, resource.cal.id),
+ &propfind.prop,
+ propfind.include.as_ref(),
+ &CalDavPrincipalUri("/caldav"),
+ &principal,
+ )
+ .unwrap();
+ let expected_output = expected_output.trim();
+ let output = response
+ .serialize_to_string()
+ .unwrap()
+ .trim()
+ .replace("\r\n", "\n");
+ println!("{output}");
+ println!("{}, {} \n\n\n", output.len(), expected_output.len());
+ assert_eq!(output, expected_output);
+ }
+ }
+}
diff --git a/crates/caldav/src/principal/tests.rs b/crates/caldav/src/principal/tests.rs
index ccdd41a..e18e6e2 100644
--- a/crates/caldav/src/principal/tests.rs
+++ b/crates/caldav/src/principal/tests.rs
@@ -1,14 +1,19 @@
use std::sync::Arc;
-use crate::principal::PrincipalResourceService;
+use crate::{
+ CalDavPrincipalUri,
+ principal::{PrincipalResource, PrincipalResourceService},
+};
use rstest::rstest;
-use rustical_dav::resource::ResourceService;
+use rustical_dav::resource::{Resource, ResourceService};
+use rustical_store::auth::{Principal, PrincipalType::Individual};
use rustical_store_sqlite::{
SqliteStore,
calendar_store::SqliteCalendarStore,
principal_store::SqlitePrincipalStore,
tests::{get_test_calendar_store, get_test_principal_store, get_test_subscription_store},
};
+use rustical_xml::XmlSerializeRoot;
#[rstest]
#[tokio::test]
@@ -44,4 +49,35 @@ async fn test_principal_resource(
}
#[tokio::test]
-async fn test_propfind() {}
+async fn test_propfind() {
+ let propfind = PrincipalResource::parse_propfind(
+ r#""#,
+ )
+ .unwrap();
+
+ let principal = Principal {
+ id: "user".to_string(),
+ displayname: None,
+ principal_type: Individual,
+ password: None,
+ memberships: vec!["group".to_string()],
+ };
+
+ let resource = PrincipalResource {
+ principal: principal.clone(),
+ members: vec![],
+ simplified_home_set: false,
+ };
+
+ let response = resource
+ .propfind(
+ &format!("/caldav/principal/{}", principal.id),
+ &propfind.prop,
+ propfind.include.as_ref(),
+ &CalDavPrincipalUri("/caldav"),
+ &principal,
+ )
+ .unwrap();
+
+ let output = response.serialize_to_string().unwrap();
+}
diff --git a/crates/ical/src/icalendar/object.rs b/crates/ical/src/icalendar/object.rs
index f6d4af7..8a4295e 100644
--- a/crates/ical/src/icalendar/object.rs
+++ b/crates/ical/src/icalendar/object.rs
@@ -6,11 +6,12 @@ use chrono::Utc;
use derive_more::Display;
use ical::generator::{Emitter, IcalCalendar};
use ical::property::Property;
+use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::{collections::HashMap, io::BufReader};
-#[derive(Debug, Clone, Serialize, PartialEq, Eq, Display)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display)]
// specified in https://datatracker.ietf.org/doc/html/rfc5545#section-3.6
pub enum CalendarObjectType {
#[serde(rename = "VEVENT")]
diff --git a/crates/store/src/calendar.rs b/crates/store/src/calendar.rs
index 7b67580..94697ab 100644
--- a/crates/store/src/calendar.rs
+++ b/crates/store/src/calendar.rs
@@ -3,9 +3,9 @@ use std::str::FromStr;
use crate::synctoken::format_synctoken;
use chrono::NaiveDateTime;
use rustical_ical::CalendarObjectType;
-use serde::Serialize;
+use serde::{Deserialize, Serialize};
-#[derive(Debug, Default, Clone, Serialize)]
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Calendar {
pub principal: String,
pub id: String,