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 @@ -973,6 +973,17 @@ mod tests { rdr.find().await.unwrap() } + #[tokio::test] + async fn simple_comp_filter() { + let expected = CompFilter { + name: Component::VEvent, + additional_rules: None, + }; + let src = r#""#; + let got = deserialize::(src).await; + assert_eq!(got, expected); + } + #[tokio::test] async fn basic_mkcalendar() { let expected = MkCalendar(dav::Set(dav::PropValue(vec![dav::Property::DisplayName( 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 Reader { } pub async fn maybe_find>(&mut self) -> Result, 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 Reader { 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#" @@ -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#" + + + + + + + + + + + "#; + 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::>(&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 +"#;