chore(test): rfc4918_proppatch is covered by Hurl

this but manifestonly with lookup by path, which is why hurl
    tests on API did not see it
This commit is contained in:
Edouard Vanbelle
2026-06-30 22:31:51 +02:00
parent 125eb66098
commit 2942b9b01b
-343
View File
@@ -1,343 +0,0 @@
//! RFC 4918 §9.2 PROPPATCH compliance — dead property storage and retrieval.
use reqwest::Method;
use super::harness::{get_server, unique_name};
fn propfind() -> Method {
Method::from_bytes(b"PROPFIND").unwrap()
}
fn proppatch() -> Method {
Method::from_bytes(b"PROPPATCH").unwrap()
}
/// PROPPATCH set a custom property → 207 with 200 propstat.
#[tokio::test]
async fn proppatch_set_returns_207() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_set"));
let (k, v) = srv.auth();
srv.client()
.put(srv.url(&path))
.header(k, v.clone())
.body("x")
.send()
.await
.unwrap();
let xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set>
<D:prop>
<Z:author>Alice</Z:author>
</D:prop>
</D:set>
</D:propertyupdate>"#;
let res = srv
.client()
.request(proppatch(), srv.url(&path))
.header(k, v)
.header("Content-Type", "application/xml")
.body(xml)
.send()
.await
.unwrap();
assert_eq!(res.status(), 207, "PROPPATCH must return 207");
let body = res.text().await.unwrap();
assert!(
body.contains("200") || body.contains("HTTP/1.1 200"),
"PROPPATCH 207 must contain 200 propstat; body: {body}"
);
}
/// PROPPATCH set → PROPFIND retrieves the stored value.
#[tokio::test]
async fn proppatch_set_property_visible_in_propfind() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_roundtrip"));
let (k, v) = srv.auth();
srv.client()
.put(srv.url(&path))
.header(k, v.clone())
.body("data")
.send()
.await
.unwrap();
// Set dead property
let set_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set>
<D:prop>
<Z:color>blue</Z:color>
</D:prop>
</D:set>
</D:propertyupdate>"#;
let pp_res = srv
.client()
.request(proppatch(), srv.url(&path))
.header(k, v.clone())
.header("Content-Type", "application/xml")
.body(set_xml)
.send()
.await
.unwrap();
assert_eq!(pp_res.status(), 207, "PROPPATCH set must return 207");
// Retrieve via PROPFIND allprop
let pf_res = srv
.client()
.request(propfind(), srv.url(&path))
.header(k, v)
.header("Depth", "0")
.send()
.await
.unwrap();
assert_eq!(pf_res.status(), 207);
let body = pf_res.text().await.unwrap();
assert!(
body.contains("color") || body.contains("blue"),
"PROPFIND allprop must include dead property set by PROPPATCH; body: {body}"
);
}
/// PROPPATCH remove → property absent from subsequent PROPFIND.
#[tokio::test]
async fn proppatch_remove_property_not_in_propfind() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_remove"));
let (k, v) = srv.auth();
srv.client()
.put(srv.url(&path))
.header(k, v.clone())
.body("data")
.send()
.await
.unwrap();
// First set
let set_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:tag>removeme</Z:tag></D:prop></D:set>
</D:propertyupdate>"#;
srv.client()
.request(proppatch(), srv.url(&path))
.header(k, v.clone())
.header("Content-Type", "application/xml")
.body(set_xml)
.send()
.await
.unwrap();
// Then remove
let remove_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:remove><D:prop><Z:tag/></D:prop></D:remove>
</D:propertyupdate>"#;
let rem_res = srv
.client()
.request(proppatch(), srv.url(&path))
.header(k, v.clone())
.header("Content-Type", "application/xml")
.body(remove_xml)
.send()
.await
.unwrap();
assert_eq!(rem_res.status(), 207, "PROPPATCH remove must return 207");
// Verify gone — request the specific prop, expect 404 propstat
let pf_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:prop><Z:tag/></D:prop>
</D:propfind>"#;
let pf_res = srv
.client()
.request(propfind(), srv.url(&path))
.header(k, v)
.header("Depth", "0")
.header("Content-Type", "application/xml")
.body(pf_xml)
.send()
.await
.unwrap();
assert_eq!(pf_res.status(), 207);
let body = pf_res.text().await.unwrap();
assert!(
body.contains("404"),
"Removed dead property must appear in 404 propstat; body: {body}"
);
}
/// PROPPATCH set + remove in same request → both applied atomically.
#[tokio::test]
async fn proppatch_set_and_remove_in_same_request() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_setrem"));
let (k, v) = srv.auth();
srv.client()
.put(srv.url(&path))
.header(k, v.clone())
.body("x")
.send()
.await
.unwrap();
// Pre-seed a property to remove
let seed_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:old>gone</Z:old></D:prop></D:set>
</D:propertyupdate>"#;
srv.client()
.request(proppatch(), srv.url(&path))
.header(k, v.clone())
.header("Content-Type", "application/xml")
.body(seed_xml)
.send()
.await
.unwrap();
// Set new + remove old in one request
let xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:new>here</Z:new></D:prop></D:set>
<D:remove><D:prop><Z:old/></D:prop></D:remove>
</D:propertyupdate>"#;
let res = srv
.client()
.request(proppatch(), srv.url(&path))
.header(k, v)
.header("Content-Type", "application/xml")
.body(xml)
.send()
.await
.unwrap();
assert_eq!(res.status(), 207, "combined set+remove must return 207");
let body = res.text().await.unwrap();
// Both ops should succeed
assert!(
!body.contains("409") && !body.contains("403"),
"combined PROPPATCH must not fail; body: {body}"
);
}
/// PROPPATCH on non-existent resource → 404.
#[tokio::test]
async fn proppatch_nonexistent_resource_returns_404() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_ghost"));
let (k, v) = srv.auth();
let xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:x>y</Z:x></D:prop></D:set>
</D:propertyupdate>"#;
let res = srv
.client()
.request(proppatch(), srv.url(&path))
.header(k, v)
.header("Content-Type", "application/xml")
.body(xml)
.send()
.await
.unwrap();
assert_eq!(
res.status(),
404,
"PROPPATCH on non-existent resource must return 404"
);
}
/// PROPPATCH on collection (folder) → 207.
#[tokio::test]
async fn proppatch_on_collection_returns_207() {
let srv = get_server();
let col = format!("/webdav/{}", unique_name("pp_col"));
let (k, v) = srv.auth();
srv.client()
.request(Method::from_bytes(b"MKCOL").unwrap(), srv.url(&col))
.header(k, v.clone())
.send()
.await
.unwrap();
let xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:desc>my folder</Z:desc></D:prop></D:set>
</D:propertyupdate>"#;
let res = srv
.client()
.request(proppatch(), srv.url(&col))
.header(k, v)
.header("Content-Type", "application/xml")
.body(xml)
.send()
.await
.unwrap();
assert_eq!(res.status(), 207, "PROPPATCH on collection must return 207");
}
/// PROPFIND specific dead property returns value in 200 propstat (not 404).
#[tokio::test]
async fn propfind_specific_dead_property_returns_200_propstat() {
let srv = get_server();
let path = format!("/webdav/{}", unique_name("pp_specific"));
let (k, v) = srv.auth();
srv.client()
.put(srv.url(&path))
.header(k, v.clone())
.body("x")
.send()
.await
.unwrap();
// Set
let set_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:set><D:prop><Z:rating>5</Z:rating></D:prop></D:set>
</D:propertyupdate>"#;
srv.client()
.request(proppatch(), srv.url(&path))
.header(k, v.clone())
.header("Content-Type", "application/xml")
.body(set_xml)
.send()
.await
.unwrap();
// PROPFIND for that exact property
let pf_xml = r#"<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:" xmlns:Z="http://example.com/ns/">
<D:prop><Z:rating/></D:prop>
</D:propfind>"#;
let pf_res = srv
.client()
.request(propfind(), srv.url(&path))
.header(k, v)
.header("Depth", "0")
.header("Content-Type", "application/xml")
.body(pf_xml)
.send()
.await
.unwrap();
assert_eq!(pf_res.status(), 207);
let body = pf_res.text().await.unwrap();
assert!(
!body.contains("404"),
"Known dead property must not be in 404 propstat; body: {body}"
);
assert!(
body.contains("rating") || body.contains("5"),
"Response must include the dead property value; body: {body}"
);
}