diff --git a/src/integration_tests/carddav/addressbook.rs b/src/integration_tests/carddav/addressbook.rs
index 0f1aae7..76d165e 100644
--- a/src/integration_tests/carddav/addressbook.rs
+++ b/src/integration_tests/carddav/addressbook.rs
@@ -192,3 +192,256 @@ async fn test_carddav_addressbook(
Err(rustical_store::Error::NotFound)
));
}
+
+#[rstest]
+#[tokio::test]
+async fn test_mkcol_rfc6352_6_3_1_1(
+ #[from(test_store_context)]
+ #[future]
+ context: TestStoreContext,
+) {
+ let context = context.await;
+ let app = get_app(context.clone());
+ let addr_store = context.addr_store;
+
+ let (displayname, description) = (
+ "Lisa's Contacts".to_owned(),
+ "My primary address book.".to_owned(),
+ );
+ let (principal, addr_id) = ("user", "contacts");
+ let url = format!("/carddav/principal/{principal}/{addr_id}");
+
+ let mut request = Request::builder()
+ .method("MKCOL")
+ .uri(&url)
+ .body(Body::from(format!(
+ r#"
+
+
+
+
+
+
+
+ {displayname}
+ {description}
+
+
+ "#
+ )))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CREATED);
+ let body = response.extract_string().await;
+ insta::assert_snapshot!("mkcol_body", body);
+ let saved_addressbook = addr_store
+ .get_addressbook(principal, addr_id, false)
+ .await
+ .unwrap();
+ assert_eq!(
+ (
+ saved_addressbook.displayname.unwrap(),
+ saved_addressbook.description.unwrap()
+ ),
+ (displayname, description)
+ );
+
+ let vcard = r"BEGIN:VCARD
+VERSION:3.0
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA
+EMAIL;TYPE=INTERNET,PREF:cyrus@example.com
+NICKNAME:me
+NOTE:Example VCard.
+ORG:Self Employed
+TEL;TYPE=WORK,VOICE:412 605 0499
+TEL;TYPE=FAX:412 605 0705
+URL:http://www.example.com
+UID:1234-5678-9000-1
+END:VCARD
+ ";
+
+ let mut request = Request::builder()
+ .method("PUT")
+ .uri(format!("{url}/newcard.vcf"))
+ .header("If-None-Match", "*")
+ .header("Content-Type", "text/vcard")
+ .body(Body::from(vcard))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CREATED);
+ let etag = response.headers().get("ETag").unwrap();
+
+ // This should overwrite
+ let mut request = Request::builder()
+ .method("PUT")
+ .uri(format!("{url}/newcard.vcf"))
+ .header("If-None-Match", "\"somearbitraryetag\"")
+ .header("Content-Type", "text/vcard")
+ .body(Body::from(vcard))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CREATED);
+
+ let mut request = Request::builder()
+ .method("PUT")
+ .uri(format!("{url}/newcard.vcf"))
+ .header("If-None-Match", etag)
+ .header("Content-Type", "text/vcard")
+ .body(Body::from(vcard))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CONFLICT);
+
+ let mut request = Request::builder()
+ .method("PUT")
+ .uri(format!("{url}/newcard.vcf"))
+ .header("If-None-Match", "*")
+ .header("Content-Type", "text/vcard")
+ .body(Body::from(vcard))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CONFLICT);
+}
+
+#[rstest]
+#[tokio::test]
+async fn test_rfc6352_8_7_1(
+ #[from(test_store_context)]
+ #[future]
+ context: TestStoreContext,
+) {
+ let context = context.await;
+ let app = get_app(context.clone());
+ let addr_store = context.addr_store;
+
+ let (displayname, description) = (
+ "Lisa's Contacts".to_owned(),
+ "My primary address book.".to_owned(),
+ );
+ let (principal, addr_id) = ("user", "contacts");
+ let url = format!("/carddav/principal/{principal}/{addr_id}");
+
+ let mut request = Request::builder()
+ .method("MKCOL")
+ .uri(&url)
+ .body(Body::from(format!(
+ r#"
+
+
+
+
+
+
+
+ {displayname}
+ {description}
+
+
+ "#
+ )))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CREATED);
+ let body = response.extract_string().await;
+ insta::assert_snapshot!("mkcol_body", body);
+ let saved_addressbook = addr_store
+ .get_addressbook(principal, addr_id, false)
+ .await
+ .unwrap();
+ assert_eq!(
+ (
+ saved_addressbook.displayname.unwrap(),
+ saved_addressbook.description.unwrap()
+ ),
+ (displayname, description)
+ );
+
+ let vcard = r"BEGIN:VCARD
+VERSION:3.0
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA
+EMAIL;TYPE=INTERNET,PREF:cyrus@example.com
+NICKNAME:me
+NOTE:Example VCard.
+ORG:Self Employed
+TEL;TYPE=WORK,VOICE:412 605 0499
+TEL;TYPE=FAX:412 605 0705
+URL:http://www.example.com
+UID:1234-5678-9000-1
+END:VCARD
+ ";
+
+ let mut request = Request::builder()
+ .method("PUT")
+ .uri(format!("{url}/newcard.vcf"))
+ .header("If-None-Match", "*")
+ .header("Content-Type", "text/vcard")
+ .body(Body::from(vcard))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::CREATED);
+
+ let mut request = Request::builder()
+ .method("REPORT")
+ .uri(&url)
+ .header("Depth", "infinity")
+ .header("Content-Type", "text/xml; charset=\"utf-8\"")
+ .body(Body::from(format!(
+ r#"
+
+
+
+
+
+
+
+
+
+
+
+
+ {url}/newcard.vcf
+ /home/bernard/addressbook/vcf1.vcf
+
+ "#
+ )))
+ .unwrap();
+ request
+ .headers_mut()
+ .typed_insert(Authorization::basic("user", "pass"));
+ let response = app.clone().oneshot(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::MULTI_STATUS);
+ let body = response.extract_string().await;
+ insta::assert_snapshot!("multiget_body", body);
+}
diff --git a/src/integration_tests/carddav/snapshots/rustical__integration_tests__carddav__addressbook__multiget_body.snap b/src/integration_tests/carddav/snapshots/rustical__integration_tests__carddav__addressbook__multiget_body.snap
new file mode 100644
index 0000000..147559f
--- /dev/null
+++ b/src/integration_tests/carddav/snapshots/rustical__integration_tests__carddav__addressbook__multiget_body.snap
@@ -0,0 +1,35 @@
+---
+source: src/integration_tests/carddav/addressbook.rs
+expression: body
+---
+
+
+
+ /carddav/principal/user/contacts/newcard.vcf
+
+
+ "24835b6c11816c864f9edadd4c7c296234c643892afcbbc5fbf5c9b7ac935cf8"
+ BEGIN:VCARD
+VERSION:3.0
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA
+EMAIL;TYPE=INTERNET,PREF:cyrus@example.com
+NICKNAME:me
+NOTE:Example VCard.
+ORG:Self Employed
+TEL;TYPE=WORK,VOICE:412 605 0499
+TEL;TYPE=FAX:412 605 0705
+URL:http://www.example.com
+UID:1234-5678-9000-1
+END:VCARD
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /home/bernard/addressbook/vcf1.vcf
+ HTTP/1.1 404 Not Found
+
+