Store vanished IMAP UIDs and WebDAV paths

This commit is contained in:
mdecimus
2025-05-29 13:38:41 +02:00
parent b793ed7ebb
commit 42fd7d4de1
25 changed files with 756 additions and 129 deletions

View File

@@ -166,7 +166,7 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection) {
imap.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_count("VANISHED", 1)
.assert_contains("VANISHED (EARLIER) 1:2") // .assert_contains("VANISHED (EARLIER) 2")
.assert_contains("VANISHED (EARLIER) 2")
.assert_count("FETCH (", 3);
// Fetch changes since SEQ 4
@@ -178,7 +178,7 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection) {
imap.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_count("VANISHED", 1)
.assert_contains("VANISHED (EARLIER) 1:2") // .assert_contains("VANISHED (EARLIER) 2")
.assert_contains("VANISHED (EARLIER) 2")
.assert_count("FETCH (", 2);
// Fetch changes since SEQ 6
@@ -190,7 +190,7 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection) {
imap.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_count("VANISHED", 1)
.assert_contains("VANISHED (EARLIER) 1:2") // .assert_contains("VANISHED (EARLIER) 2")
.assert_contains("VANISHED (EARLIER) 2")
.assert_count("FETCH (", 1);
// Fetch changes since SEQ 7
@@ -202,7 +202,7 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection) {
imap.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_count("VANISHED", 1)
.assert_contains("VANISHED (EARLIER) 1:2") // .assert_contains("VANISHED (EARLIER) 2")
.assert_contains("VANISHED (EARLIER) 2")
.assert_count("FETCH (", 0);
// Fetch changes since SEQ 8
@@ -276,5 +276,5 @@ pub async fn test(imap: &mut ImapConnection, imap_check: &mut ImapConnection) {
imap.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_count("FETCH (", 3)
.assert_contains("VANISHED (EARLIER) 1:2"); // .assert_contains("VANISHED (EARLIER) 2");
.assert_contains("VANISHED (EARLIER) 2");
}

View File

@@ -131,6 +131,24 @@ pub async fn test(test: &WebDavTest) {
replace_prefix(&mut hierarchy, &hierarchy_root, &new_hierarchy_root);
assert_result(&response, &hierarchy);
client.validate_values(&hierarchy).await;
// Validate changes
let changes = client
.sync_collection(
&user_base_path,
sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await
.with_href_count(2)
.into_propfind_response(None);
changes
.properties(&hierarchy_root)
.with_status(StatusCode::NOT_FOUND);
changes
.properties(&new_hierarchy_root)
.with_status(StatusCode::OK);
let hierarchy_root = new_hierarchy_root;
// Test 2: Copy container
@@ -167,6 +185,17 @@ pub async fn test(test: &WebDavTest) {
// Test 4: Create a shallow container and overwrite the previous one using MOVE
let (new_hierarchy_root, mut hierarchy) =
client.create_hierarchy(&user_base_path, 0, 0, 3).await;
let sync_token = client
.sync_collection(
&user_base_path,
sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await
.sync_token()
.to_string();
client
.request_with_headers(
"MOVE",
@@ -182,6 +211,23 @@ pub async fn test(test: &WebDavTest) {
replace_prefix(&mut hierarchy, &new_hierarchy_root, &hierarchy_root);
assert_result(&response, &hierarchy);
client.validate_values(&hierarchy).await;
// Validate changes
let changes = client
.sync_collection(
&user_base_path,
&sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await
.into_propfind_response(None);
changes
.properties(&new_hierarchy_root)
.with_status(StatusCode::NOT_FOUND);
changes
.properties(&hierarchy_root)
.with_status(StatusCode::OK);
// Test 5: Create a deep container and overwrite the previous one using COPY
let (new_hierarchy_root, new_hierarchy) = client

View File

@@ -710,7 +710,11 @@ pub struct DavItem {
}
#[derive(Debug, serde::Serialize)]
pub struct DavProperties(Vec<DavItem>);
pub struct DavProperties {
#[serde(skip)]
status: StatusCode,
props: Vec<DavItem>,
}
impl DavMultiStatus {
pub fn properties(&self, href: &str) -> DavPropertyResult<'_> {
@@ -752,7 +756,7 @@ impl DavPropertyResult<'_> {
pub fn get(&self, name: impl AsRef<str>) -> DavQueryResult<'_> {
let name = name.as_ref();
self.properties
.0
.props
.iter()
.find_map(|prop| {
prop.values.get(name).map(|values| DavQueryResult {
@@ -765,15 +769,26 @@ impl DavPropertyResult<'_> {
self.response.dump_response();
panic!(
"No property found for name: {name} in {}",
serde_json::to_string_pretty(&self.properties.0).unwrap()
serde_json::to_string_pretty(&self.properties.props).unwrap()
)
})
}
pub fn with_status(&self, status: StatusCode) -> &Self {
if self.properties.status != status {
self.response.dump_response();
panic!(
"Expected status {status}, but got {}",
self.properties.status
);
}
self
}
pub fn is_defined(&self, name: impl AsRef<str>) -> &Self {
if self
.properties
.0
.props
.iter()
.any(|prop| prop.values.contains_key(name.as_ref()))
{
@@ -787,7 +802,7 @@ impl DavPropertyResult<'_> {
pub fn is_undefined(&self, name: impl AsRef<str>) -> &Self {
if self
.properties
.0
.props
.iter()
.any(|prop| prop.values.contains_key(name.as_ref()))
{
@@ -931,6 +946,7 @@ impl DavResponse {
hrefs: AHashMap::new(),
};
let mut href = None;
let mut href_status = StatusCode::OK;
let mut props = Vec::new();
let mut prop = DavItem::default();
@@ -941,12 +957,25 @@ impl DavResponse {
if !prop.is_empty() {
props.push(std::mem::take(&mut prop));
}
result
.hrefs
.insert(href, DavProperties(std::mem::take(&mut props)));
result.hrefs.insert(
href,
DavProperties {
status: href_status,
props: std::mem::take(&mut props),
},
);
href_status = StatusCode::OK;
}
href = Some(value.to_string());
}
"D:multistatus.D:response.D:status" => {
href_status = value
.split_ascii_whitespace()
.nth(1)
.unwrap_or_default()
.parse()
.unwrap();
}
"D:multistatus.D:response.D:propstat.D:status" => {
prop.status = value
.split_ascii_whitespace()
@@ -958,7 +987,6 @@ impl DavResponse {
"D:multistatus.D:response.D:propstat.D:responsedescription" => {
prop.description = Some(value.to_string());
}
_ => {
if let Some(prop_name) =
key.strip_prefix("D:multistatus.D:response.D:propstat.D:prop.")
@@ -990,7 +1018,13 @@ impl DavResponse {
if !prop.is_empty() {
props.push(prop);
}
result.hrefs.insert(href, DavProperties(props));
result.hrefs.insert(
href,
DavProperties {
status: href_status,
props,
},
);
}
result

View File

@@ -223,15 +223,68 @@ pub async fn test(test: &WebDavTest) {
}
assert!(expected_changes.is_empty(), "{:?}", expected_changes);
// Test 10: Expect changes after deletion
client
.request("DELETE", &folder_name, "")
.request("DELETE", &new_file, "")
.await
.with_status(StatusCode::NO_CONTENT);
let response = client
.sync_collection(
&user_base_path,
&sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await;
sync_token = response.sync_token().to_string();
response
.with_href_count(1)
.with_value("D:multistatus.D:response.D:href", &new_file)
.with_value(
"D:multistatus.D:response.D:status",
"HTTP/1.1 404 Not Found",
);
client
.request("DELETE", &new_collection, "")
.await
.with_status(StatusCode::NO_CONTENT);
let response = client
.sync_collection(
&user_base_path,
&sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await;
sync_token = response.sync_token().to_string();
response
.with_href_count(1)
.with_value("D:multistatus.D:response.D:href", &new_collection)
.with_value(
"D:multistatus.D:response.D:status",
"HTTP/1.1 404 Not Found",
);
client
.request("DELETE", &folder_name, "")
.await
.with_status(StatusCode::NO_CONTENT);
client
.sync_collection(
&user_base_path,
&sync_token,
Depth::Infinity,
None,
["D:getetag"],
)
.await
.with_href_count(1)
.with_value("D:multistatus.D:response.D:href", &folder_name)
.with_value(
"D:multistatus.D:response.D:status",
"HTTP/1.1 404 Not Found",
);
}
client.delete_default_containers().await;