aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-05-23 08:55:53 +0200
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-05-23 08:55:53 +0200
commita859fe38b1044c576f042254a0f9677054b417a0 (patch)
tree19a009c49f433afefd930f37db12cec8aef9f255
parent54d10ed48274607c7bc4e0fd5fb1919f57317b70 (diff)
downloadaerogramme-a859fe38b1044c576f042254a0f9677054b417a0.tar.gz
aerogramme-a859fe38b1044c576f042254a0f9677054b417a0.zip
test calendar-query vevent filtering
-rw-r--r--aero-dav/src/caldecoder.rs11
-rw-r--r--aero-dav/src/xml.rs6
-rw-r--r--aerogramme/tests/behavior.rs124
-rw-r--r--aerogramme/tests/common/constants.rs34
4 files changed, 174 insertions, 1 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs
index 02991c2..6bc911f 100644
--- a/aero-dav/src/caldecoder.rs
+++ b/aero-dav/src/caldecoder.rs
@@ -974,6 +974,17 @@ mod tests {
}
#[tokio::test]
+ async fn simple_comp_filter() {
+ let expected = CompFilter {
+ name: Component::VEvent,
+ additional_rules: None,
+ };
+ let src = r#"<C:comp-filter name="VEVENT" xmlns:C="urn:ietf:params:xml:ns:caldav" />"#;
+ let got = deserialize::<CompFilter>(src).await;
+ assert_eq!(got, expected);
+ }
+
+ #[tokio::test]
async fn basic_mkcalendar() {
let expected = MkCalendar(dav::Set(dav::PropValue(vec![dav::Property::DisplayName(
"Lisa's Events".into(),
diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs
index c89f531..e59f136 100644
--- a/aero-dav/src/xml.rs
+++ b/aero-dav/src/xml.rs
@@ -229,7 +229,10 @@ impl<T: IRead> Reader<T> {
}
pub async fn maybe_find<N: Node<N>>(&mut self) -> Result<Option<N>, ParsingError> {
- self.ensure_parent_has_child()?;
+ // We can't find anything inside a self-closed tag
+ if !self.parent_has_child() {
+ return Ok(None);
+ }
loop {
// Try parse
@@ -238,6 +241,7 @@ impl<T: IRead> Reader<T> {
otherwise => return otherwise.map(Some),
}
+ // Skip or stop
match self.peek() {
Event::End(_) => return Ok(None),
_ => self.skip().await?,
diff --git a/aerogramme/tests/behavior.rs b/aerogramme/tests/behavior.rs
index d13e556..975dae9 100644
--- a/aerogramme/tests/behavior.rs
+++ b/aerogramme/tests/behavior.rs
@@ -553,6 +553,45 @@ fn rfc5397_webdav_principal() {
fn rfc4791_webdav_caldav() {
println!("🧪 rfc4791_webdav_caldav");
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
+ // --- INITIAL TEST SETUP ---
+ // Add entries (3 VEVENT, 1 FREEBUSY, 1 VTODO)
+ let resp = http
+ .put("http://localhost:8087/alice/calendar/Personal/rfc1.ics")
+ .header("If-None-Match", "*")
+ .body(ICAL_RFC1)
+ .send()?;
+ let obj1_etag = resp.headers().get("etag").expect("etag must be set");
+ assert_eq!(resp.status(), 201);
+ let resp = http
+ .put("http://localhost:8087/alice/calendar/Personal/rfc2.ics")
+ .header("If-None-Match", "*")
+ .body(ICAL_RFC2)
+ .send()?;
+ let obj2_etag = resp.headers().get("etag").expect("etag must be set");
+ assert_eq!(resp.status(), 201);
+ let resp = http
+ .put("http://localhost:8087/alice/calendar/Personal/rfc3.ics")
+ .header("If-None-Match", "*")
+ .body(ICAL_RFC3)
+ .send()?;
+ let obj3_etag = resp.headers().get("etag").expect("etag must be set");
+ assert_eq!(resp.status(), 201);
+ let resp = http
+ .put("http://localhost:8087/alice/calendar/Personal/rfc4.ics")
+ .header("If-None-Match", "*")
+ .body(ICAL_RFC4)
+ .send()?;
+ let obj4_etag = resp.headers().get("etag").expect("etag must be set");
+ assert_eq!(resp.status(), 201);
+ let resp = http
+ .put("http://localhost:8087/alice/calendar/Personal/rfc5.ics")
+ .header("If-None-Match", "*")
+ .body(ICAL_RFC5)
+ .send()?;
+ let obj5_etag = resp.headers().get("etag").expect("etag must be set");
+ assert_eq!(resp.status(), 201);
+
+ // --- AUTODISCOVERY ---
// Check calendar discovery from principal
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
@@ -595,7 +634,92 @@ fn rfc4791_webdav_caldav() {
.expect("request returns a calendar home set");
assert_eq!(calendar_home_set, "/alice/calendar/");
+ // Check calendar access support
+ let resp = http
+ .request(
+ reqwest::Method::from_bytes(b"OPTIONS")?,
+ "http://localhost:8087/alice/calendar/",
+ )
+ .send()?;
+ //@FIXME not yet supported. returns DAV: 1 ; expects DAV: 1 calendar-access
+
+ //@FIXME missing support for calendar-data...
+ //println!("{:?}", resp);
+
+ // --- REPORT calendar-query ---
+ // 7.8.8. Example: Retrieval of Events Only
+ let cal_query = r#"<?xml version="1.0" encoding="utf-8" ?>
+ <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
+ <D:prop xmlns:D="DAV:">
+ <D:getetag/>
+ <C:calendar-data/>
+ </D:prop>
+ <C:filter>
+ <C:comp-filter name="VCALENDAR">
+ <C:comp-filter name="VEVENT"/>
+ </C:comp-filter>
+ </C:filter>
+ </C:calendar-query>"#;
+ let resp = http
+ .request(
+ reqwest::Method::from_bytes(b"REPORT")?,
+ "http://localhost:8087/alice/calendar/Personal/",
+ )
+ .body(cal_query)
+ .send()?;
+ assert_eq!(resp.status(), 207);
+ let multistatus = dav_deserialize::<dav::Multistatus<All>>(&resp.text()?);
+ assert_eq!(multistatus.responses.len(), 3);
+ [
+ ("/alice/calendar/Personal/rfc1.ics", obj1_etag, ICAL_RFC1),
+ ("/alice/calendar/Personal/rfc2.ics", obj2_etag, ICAL_RFC2),
+ ("/alice/calendar/Personal/rfc3.ics", obj3_etag, ICAL_RFC3),
+ ]
+ .iter()
+ .for_each(|(ref_path, ref_etag, ref_ical)| {
+ let obj_stats = multistatus
+ .responses
+ .iter()
+ .find_map(|v| match &v.status_or_propstat {
+ dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == *ref_path => {
+ Some(x)
+ }
+ _ => None,
+ })
+ .expect("propstats must exist");
+ let obj_success = obj_stats
+ .iter()
+ .find(|p| p.status.0.as_u16() == 200)
+ .expect("some propstats must be 200");
+ let etag = obj_success
+ .prop
+ .0
+ .iter()
+ .find_map(|p| match p {
+ dav::AnyProperty::Value(dav::Property::GetEtag(x)) => Some(x),
+ _ => None,
+ })
+ .expect("etag is return in propstats");
+ assert_eq!(
+ etag.as_str(),
+ ref_etag
+ .to_str()
+ .expect("header value is convertible to string")
+ );
+ let calendar_data = obj_success
+ .prop
+ .0
+ .iter()
+ .find_map(|p| match p {
+ dav::AnyProperty::Value(dav::Property::Extension(
+ realization::Property::Cal(cal::Property::CalendarData(x)),
+ )) => Some(x),
+ _ => None,
+ })
+ .expect("calendar data is returned in propstats");
+ assert_eq!(calendar_data.payload.as_bytes(), *ref_ical);
+ });
Ok(())
})
diff --git a/aerogramme/tests/common/constants.rs b/aerogramme/tests/common/constants.rs
index 6b17c4f..8874876 100644
--- a/aerogramme/tests/common/constants.rs
+++ b/aerogramme/tests/common/constants.rs
@@ -124,3 +124,37 @@ UID:DC6C50A017428C5216A2F1CD@example.com
END:VEVENT
END:VCALENDAR
";
+
+pub static ICAL_RFC4: &[u8] = br#"BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Example Corp.//CalDAV Client//EN
+BEGIN:VFREEBUSY
+ORGANIZER;CN="Bernard Desruisseaux":mailto:bernard@example.com
+UID:76ef34-54a3d2@example.com
+DTSTAMP:20050530T123421Z
+DTSTART:20060101T000000Z
+DTEND:20060108T000000Z
+FREEBUSY:20050531T230000Z/20050601T010000Z
+FREEBUSY;FBTYPE=BUSY-TENTATIVE:20060102T100000Z/20060102T120000Z
+FREEBUSY:20060103T100000Z/20060103T120000Z
+FREEBUSY:20060104T100000Z/20060104T120000Z
+FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20060105T100000Z/20060105T120000Z
+FREEBUSY:20060106T100000Z/20060106T120000Z
+END:VFREEBUSY
+END:VCALENDAR
+"#;
+
+pub static ICAL_RFC5: &[u8] = br#"BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Example Corp.//CalDAV Client//EN
+BEGIN:VTODO
+DTSTAMP:20060205T235600Z
+DUE;VALUE=DATE:20060101
+LAST-MODIFIED:20060205T235308Z
+SEQUENCE:1
+STATUS:CANCELLED
+SUMMARY:Task #4
+UID:E10BA47467C5C69BB74E8725@example.com
+END:VTODO
+END:VCALENDAR
+"#;