WebDAV REPORT expand-property, principal-*
This commit is contained in:
@@ -81,8 +81,15 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await
|
||||
.with_status(StatusCode::CREATED);
|
||||
|
||||
// Test 5: Creating a file under a locked resource without a lock token should fail
|
||||
// Test 5: Creating a lock under an infinity locked resource should fail
|
||||
let file_path = format!("{path}/file.txt");
|
||||
client
|
||||
.lock_create(&file_path, "super-owner", true, "0", "Second-123")
|
||||
.await
|
||||
.with_status(StatusCode::LOCKED)
|
||||
.with_value("D:error.D:lock-token-submitted.D:href", &path);
|
||||
|
||||
// Test 6: Creating a file under a locked resource without a lock token should fail
|
||||
let contents = resource_type.generate();
|
||||
client
|
||||
.request("PUT", &file_path, &contents)
|
||||
@@ -90,7 +97,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.with_status(StatusCode::LOCKED)
|
||||
.with_value("D:error.D:lock-token-submitted.D:href", &path);
|
||||
|
||||
// Test 6: Creating a file under a locked resource with a lock token should succeed
|
||||
// Test 7: Creating a file under a locked resource with a lock token should succeed
|
||||
client
|
||||
.request_with_headers(
|
||||
"PUT",
|
||||
@@ -101,7 +108,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await
|
||||
.with_status(StatusCode::CREATED);
|
||||
|
||||
// Test 7: Locks should be included in propfind responses
|
||||
// Test 8: Locks should be included in propfind responses
|
||||
let response = client
|
||||
.propfind(&path, [DavProperty::WebDav(WebDavProperty::LockDiscovery)])
|
||||
.await;
|
||||
@@ -120,7 +127,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
]);
|
||||
}
|
||||
|
||||
// Test 8: Delete with and without a lock token
|
||||
// Test 9: Delete with and without a lock token
|
||||
client
|
||||
.request("DELETE", &path, "")
|
||||
.await
|
||||
@@ -136,7 +143,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await
|
||||
.with_status(StatusCode::NO_CONTENT);
|
||||
|
||||
// Test 9: Unlock with and without a lock token
|
||||
// Test 10: Unlock with and without a lock token
|
||||
client
|
||||
.unlock(&path, "urn:stalwart:davlock:1234")
|
||||
.await
|
||||
@@ -147,7 +154,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await
|
||||
.with_status(StatusCode::NO_CONTENT);
|
||||
|
||||
// Test 10: Locking with a large dead property should fail
|
||||
// Test 11: Locking with a large dead property should fail
|
||||
let path = format!("{base_path}/invalid-lock");
|
||||
client
|
||||
.lock_create(
|
||||
@@ -163,7 +170,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await
|
||||
.with_status(StatusCode::PAYLOAD_TOO_LARGE);
|
||||
|
||||
// Test 11: Too many locks should fail
|
||||
// Test 12: Too many locks should fail
|
||||
for i in 0..test.server.core.groupware.max_locks_per_user {
|
||||
client
|
||||
.lock_create(
|
||||
|
||||
@@ -49,6 +49,7 @@ pub mod copy_move;
|
||||
pub mod lock;
|
||||
pub mod mkcol;
|
||||
pub mod multiget;
|
||||
pub mod principals;
|
||||
pub mod prop;
|
||||
pub mod put_get;
|
||||
pub mod sync;
|
||||
@@ -218,6 +219,19 @@ disabled-events = ["network.*"]
|
||||
|
||||
"#;
|
||||
|
||||
pub const TEST_DAV_USERS: &[(&str, &str, &str, &str)] = &[
|
||||
("admin", "secret1", "Superuser", "admin@example,com"),
|
||||
("john", "secret2", "John Doe", "jdoe@example.com"),
|
||||
(
|
||||
"jane",
|
||||
"secret3",
|
||||
"Jane Doe-Smith",
|
||||
"jane.smith@example.com",
|
||||
),
|
||||
("bill", "secret4", "Bill Foobar", "bill@example,com"),
|
||||
("mike", "secret5", "Mike Noquota", "mike@example,com"),
|
||||
];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct WebDavTest {
|
||||
server: Server,
|
||||
@@ -318,21 +332,15 @@ async fn init_webdav_tests(store_id: &str, delete_if_exists: bool) -> WebDavTest
|
||||
|
||||
// Create test accounts
|
||||
let mut clients = AHashMap::new();
|
||||
for (account, secret, name, email) in [
|
||||
("admin", "secret1", "Superuser", "admin@example,com"),
|
||||
("john", "secret2", "John Doe", "jdoe@example.com"),
|
||||
("jane", "secret3", "Jane Smith", "jane.smith@example.com"),
|
||||
("bill", "secret4", "Bill Foobar", "bill@example,com"),
|
||||
("mike", "secret5", "Mike Noquota", "mike@example,com"),
|
||||
] {
|
||||
for (account, secret, name, email) in TEST_DAV_USERS {
|
||||
let account_id = store
|
||||
.create_test_user(account, secret, name, &[email])
|
||||
.await;
|
||||
clients.insert(
|
||||
account,
|
||||
*account,
|
||||
DummyWebDavClient::new(account_id, account, secret, email),
|
||||
);
|
||||
if account == "mike" {
|
||||
if *account == "mike" {
|
||||
store.set_test_quota(account, 1024).await;
|
||||
}
|
||||
}
|
||||
@@ -364,11 +372,6 @@ pub async fn webdav_tests() {
|
||||
/*
|
||||
TODO:
|
||||
|
||||
- Principals:
|
||||
- PrincipalMatch
|
||||
- PrincipalPropertySearch
|
||||
- PrincipalSearchPropertySet
|
||||
- Expand property
|
||||
- ACLs:
|
||||
- ACL Method
|
||||
- AclPrincipalPropSet
|
||||
@@ -387,6 +390,7 @@ pub async fn webdav_tests() {
|
||||
multiget::test(&handle).await;
|
||||
sync::test(&handle).await;
|
||||
lock::test(&handle).await;
|
||||
principals::test(&handle).await;
|
||||
|
||||
// Print elapsed time
|
||||
let elapsed = start_time.elapsed();
|
||||
|
||||
356
tests/src/webdav/principals.rs
Normal file
356
tests/src/webdav/principals.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::WebDavTest;
|
||||
use crate::webdav::{TEST_DAV_USERS, prop::ALL_DAV_PROPERTIES};
|
||||
use dav_proto::schema::property::{DavProperty, PrincipalProperty, WebDavProperty};
|
||||
use groupware::DavResourceName;
|
||||
use hyper::StatusCode;
|
||||
|
||||
pub async fn test(test: &WebDavTest) {
|
||||
println!("Running principals tests...");
|
||||
let client = test.client("jane");
|
||||
let principal_path = format!("D:href:{}/", DavResourceName::Principal.base_path());
|
||||
let jane_principal_path = format!("D:href:{}/jane/", DavResourceName::Principal.base_path());
|
||||
|
||||
// Test 1: PROPFIND on /dav/pal should return all principals
|
||||
let response = client
|
||||
.propfind(
|
||||
DavResourceName::Principal.collection_path(),
|
||||
ALL_DAV_PROPERTIES,
|
||||
)
|
||||
.await;
|
||||
for (account, _, name, _) in TEST_DAV_USERS {
|
||||
let props = response.properties(&format!(
|
||||
"{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
account
|
||||
));
|
||||
let path_pal = format!(
|
||||
"D:href:{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
account
|
||||
);
|
||||
let path_card = format!("D:href:{}/{}/", DavResourceName::Card.base_path(), account);
|
||||
let path_cal = format!("D:href:{}/{}/", DavResourceName::Cal.base_path(), account);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::DisplayName))
|
||||
.with_values([*name])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
|
||||
.with_values([jane_principal_path.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(PrincipalProperty::PrincipalURL))
|
||||
.with_values([path_pal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::Owner))
|
||||
.with_values([path_pal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
|
||||
.with_values([path_cal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(
|
||||
PrincipalProperty::AddressbookHomeSet,
|
||||
))
|
||||
.with_values([path_card.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet))
|
||||
.with_values([principal_path.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::SupportedReportSet))
|
||||
.with_values([
|
||||
"D:supported-report.D:report.D:principal-property-search",
|
||||
"D:supported-report.D:report.D:principal-search-property-set",
|
||||
"D:supported-report.D:report.D:principal-match",
|
||||
])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
|
||||
.with_values(["D:principal", "D:collection"])
|
||||
.with_status(StatusCode::OK);
|
||||
}
|
||||
|
||||
// Test 2: PROPFIND on /dav/[resource] should return user and shared resources
|
||||
for resource_type in [
|
||||
DavResourceName::File,
|
||||
DavResourceName::Cal,
|
||||
DavResourceName::Card,
|
||||
] {
|
||||
let supported_reports = match resource_type {
|
||||
DavResourceName::File => [
|
||||
"D:supported-report.D:report.D:sync-collection",
|
||||
"D:supported-report.D:report.D:acl-principal-prop-set",
|
||||
"D:supported-report.D:report.D:principal-match",
|
||||
]
|
||||
.as_slice(),
|
||||
DavResourceName::Cal => [
|
||||
"D:supported-report.D:report.A:free-busy-query",
|
||||
"D:supported-report.D:report.A:calendar-query",
|
||||
"D:supported-report.D:report.D:expand-property",
|
||||
"D:supported-report.D:report.D:sync-collection",
|
||||
"D:supported-report.D:report.D:acl-principal-prop-set",
|
||||
"D:supported-report.D:report.D:principal-match",
|
||||
"D:supported-report.D:report.A:calendar-multiget",
|
||||
]
|
||||
.as_slice(),
|
||||
DavResourceName::Card => [
|
||||
"D:supported-report.D:report.B:addressbook-query",
|
||||
"D:supported-report.D:report.D:acl-principal-prop-set",
|
||||
"D:supported-report.D:report.D:expand-property",
|
||||
"D:supported-report.D:report.B:addressbook-multiget",
|
||||
"D:supported-report.D:report.D:principal-match",
|
||||
"D:supported-report.D:report.D:sync-collection",
|
||||
]
|
||||
.as_slice(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let privilege_set = if resource_type == DavResourceName::Cal {
|
||||
[
|
||||
"D:privilege.D:read-current-user-privilege-set",
|
||||
"D:privilege.D:write-acl",
|
||||
"D:privilege.A:read-free-busy",
|
||||
"D:privilege.D:read-acl",
|
||||
"D:privilege.D:write-properties",
|
||||
"D:privilege.D:write",
|
||||
"D:privilege.D:write-content",
|
||||
"D:privilege.D:unlock",
|
||||
"D:privilege.D:all",
|
||||
"D:privilege.D:read",
|
||||
"D:privilege.D:bind",
|
||||
"D:privilege.D:unbind",
|
||||
]
|
||||
.as_slice()
|
||||
} else {
|
||||
[
|
||||
"D:privilege.D:all",
|
||||
"D:privilege.D:read",
|
||||
"D:privilege.D:write",
|
||||
"D:privilege.D:write-properties",
|
||||
"D:privilege.D:write-content",
|
||||
"D:privilege.D:unlock",
|
||||
"D:privilege.D:read-acl",
|
||||
"D:privilege.D:read-current-user-privilege-set",
|
||||
"D:privilege.D:write-acl",
|
||||
"D:privilege.D:bind",
|
||||
"D:privilege.D:unbind",
|
||||
]
|
||||
.as_slice()
|
||||
};
|
||||
|
||||
let response = client
|
||||
.propfind(resource_type.collection_path(), ALL_DAV_PROPERTIES)
|
||||
.await;
|
||||
let props = response.properties(resource_type.collection_path());
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::SupportedReportSet))
|
||||
.with_values(supported_reports.iter().copied())
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
|
||||
.with_values(["D:collection"])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
|
||||
.with_values([jane_principal_path.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
|
||||
.with_values([format!("D:href:{}/jane/", DavResourceName::Cal.base_path()).as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(
|
||||
PrincipalProperty::AddressbookHomeSet,
|
||||
))
|
||||
.with_values([format!("D:href:{}/jane/", DavResourceName::Card.base_path()).as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
|
||||
for (account, _, name, _) in TEST_DAV_USERS
|
||||
.iter()
|
||||
.filter(|(account, _, _, _)| ["jane", "support"].contains(account))
|
||||
{
|
||||
let path_card = format!("D:href:{}/{}/", DavResourceName::Card.base_path(), account);
|
||||
let path_cal = format!("D:href:{}/{}/", DavResourceName::Cal.base_path(), account);
|
||||
let path_pal = format!(
|
||||
"D:href:{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
account
|
||||
);
|
||||
let props = response.properties(&format!("{}/{account}/", resource_type.base_path()));
|
||||
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::DisplayName))
|
||||
.with_values([*name])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
|
||||
.with_values(["D:collection"])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
|
||||
.with_values([jane_principal_path.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet))
|
||||
.with_values(privilege_set.iter().copied())
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::SupportedReportSet))
|
||||
.with_values(supported_reports.iter().copied())
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(PrincipalProperty::PrincipalURL))
|
||||
.with_values([path_pal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet))
|
||||
.with_values([principal_path.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::Owner))
|
||||
.with_values([path_pal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
|
||||
.with_values([path_cal.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::Principal(
|
||||
PrincipalProperty::AddressbookHomeSet,
|
||||
))
|
||||
.with_values([path_card.as_str()])
|
||||
.with_status(StatusCode::OK);
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::SyncToken))
|
||||
.with_status(StatusCode::OK)
|
||||
.is_not_empty();
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::QuotaAvailableBytes))
|
||||
.with_status(StatusCode::OK)
|
||||
.is_not_empty();
|
||||
props
|
||||
.get(DavProperty::WebDav(WebDavProperty::QuotaUsedBytes))
|
||||
.with_status(StatusCode::OK)
|
||||
.is_not_empty();
|
||||
}
|
||||
|
||||
// Test 3: principal-match-query on resources
|
||||
let response = client
|
||||
.request(
|
||||
"REPORT",
|
||||
resource_type.collection_path(),
|
||||
PRINCIPAL_MATCH_QUERY,
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None);
|
||||
response.with_hrefs([
|
||||
format!("{}/jane/", resource_type.base_path()).as_str(),
|
||||
format!("{}/support/", resource_type.base_path()).as_str(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Test 4: principal-match-query on principals
|
||||
let response = client
|
||||
.request(
|
||||
"REPORT",
|
||||
DavResourceName::Principal.collection_path(),
|
||||
PRINCIPAL_MATCH_QUERY,
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None);
|
||||
response.with_hrefs([
|
||||
format!("{}/jane/", DavResourceName::Principal.base_path()).as_str(),
|
||||
format!("{}/support/", DavResourceName::Principal.base_path()).as_str(),
|
||||
]);
|
||||
|
||||
// Test 5: principal-search-property-set REPORT
|
||||
let response = client
|
||||
.request(
|
||||
"REPORT",
|
||||
DavResourceName::Principal.collection_path(),
|
||||
PRINCIPAL_SEARCH_PROPERTY_SET_QUERY,
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::OK);
|
||||
response
|
||||
.with_value(
|
||||
"D:principal-search-property-set.D:principal-search-property.D:prop.D:displayname",
|
||||
"",
|
||||
)
|
||||
.with_value(
|
||||
"D:principal-search-property-set.D:principal-search-property.D:description",
|
||||
"Account or Group name",
|
||||
);
|
||||
|
||||
// Test 6: principal-property-search REPORT
|
||||
let response = client
|
||||
.request(
|
||||
"REPORT",
|
||||
DavResourceName::Principal.collection_path(),
|
||||
PRINCIPAL_PROPERTY_SEARCH_QUERY.replace("$NAME", "doe"),
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None);
|
||||
response.with_hrefs([
|
||||
format!("{}/jane/", DavResourceName::Principal.base_path()).as_str(),
|
||||
format!("{}/john/", DavResourceName::Principal.base_path()).as_str(),
|
||||
]);
|
||||
response
|
||||
.properties(&format!("{}/jane/", DavResourceName::Principal.base_path()))
|
||||
.get(DavProperty::WebDav(WebDavProperty::DisplayName))
|
||||
.with_values([TEST_DAV_USERS
|
||||
.iter()
|
||||
.find(|(account, _, _, _)| *account == "jane")
|
||||
.unwrap()
|
||||
.2])
|
||||
.with_status(StatusCode::OK);
|
||||
client
|
||||
.request(
|
||||
"REPORT",
|
||||
DavResourceName::Principal.collection_path(),
|
||||
PRINCIPAL_PROPERTY_SEARCH_QUERY.replace("$NAME", "support"),
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None)
|
||||
.with_hrefs([format!("{}/support/", DavResourceName::Principal.base_path()).as_str()]);
|
||||
|
||||
client.delete_default_containers().await;
|
||||
test.assert_is_empty().await;
|
||||
}
|
||||
|
||||
const PRINCIPAL_MATCH_QUERY: &str = r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:principal-match xmlns:D="DAV:">
|
||||
<D:principal-property>
|
||||
<D:owner/>
|
||||
<D:displayname/>
|
||||
</D:principal-property>
|
||||
</D:principal-match>"#;
|
||||
|
||||
const PRINCIPAL_SEARCH_PROPERTY_SET_QUERY: &str =
|
||||
r#"<?xml version="1.0" encoding="utf-8" ?><D:principal-search-property-set xmlns:D="DAV:"/>"#;
|
||||
|
||||
const PRINCIPAL_PROPERTY_SEARCH_QUERY: &str = r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:principal-property-search xmlns:D="DAV:">
|
||||
<D:property-search>
|
||||
<D:prop>
|
||||
<D:displayname/>
|
||||
</D:prop>
|
||||
<D:match>$NAME</D:match>
|
||||
</D:property-search>
|
||||
<D:prop xmlns:B="http://www.example.com/ns/">
|
||||
<D:displayname/>
|
||||
</D:prop>
|
||||
</D:principal-property-search>"#;
|
||||
@@ -85,20 +85,23 @@ pub async fn test(test: &WebDavTest) {
|
||||
}
|
||||
|
||||
// Test 5: PROPFIND Depth 1 on user base path
|
||||
let response = client
|
||||
client
|
||||
.request_with_headers("PROPFIND", &user_base_path, [("depth", "1")], "")
|
||||
.await;
|
||||
if resource_type != DavResourceName::File {
|
||||
response.with_status(StatusCode::MULTI_STATUS).with_hrefs([
|
||||
format!("{user_base_path}/").as_str(),
|
||||
format!("{user_base_path}/default/").as_str(),
|
||||
&test_base_path,
|
||||
]);
|
||||
} else {
|
||||
response
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.with_hrefs([format!("{user_base_path}/").as_str(), &test_base_path]);
|
||||
}
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.with_hrefs(
|
||||
[
|
||||
format!("{user_base_path}/default/").as_str(),
|
||||
format!("{user_base_path}/").as_str(),
|
||||
&test_base_path,
|
||||
]
|
||||
.into_iter()
|
||||
.skip(if resource_type == DavResourceName::File {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
);
|
||||
|
||||
// Test 6: PROPFIND Depth 1 on created collection
|
||||
client
|
||||
@@ -117,7 +120,60 @@ pub async fn test(test: &WebDavTest) {
|
||||
StatusCode::MULTI_STATUS
|
||||
});
|
||||
|
||||
// Test 8: Retrieve all static properties
|
||||
// Test 8 PROPFIND with depth-no-root
|
||||
client
|
||||
.request_with_headers(
|
||||
"PROPFIND",
|
||||
&user_base_path,
|
||||
[("depth", "1"), ("prefer", "depth-noroot")],
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.with_hrefs(
|
||||
[
|
||||
format!("{user_base_path}/default/").as_str(),
|
||||
&test_base_path,
|
||||
]
|
||||
.into_iter()
|
||||
.skip(if resource_type == DavResourceName::File {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
);
|
||||
client
|
||||
.request_with_headers(
|
||||
"PROPFIND",
|
||||
&test_base_path,
|
||||
[("depth", "1"), ("prefer", "depth-noroot")],
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.with_hrefs([test_path.as_str()]);
|
||||
|
||||
// Test 8 PROPFIND with prefer return=minimal
|
||||
let response = client
|
||||
.propfind_with_headers(&test_base_path, ALL_DAV_PROPERTIES, [])
|
||||
.await;
|
||||
response
|
||||
.properties(&test_base_path)
|
||||
.is_defined(DavProperty::WebDav(WebDavProperty::GetETag))
|
||||
.is_defined(DavProperty::Principal(PrincipalProperty::GroupMembership));
|
||||
let response = client
|
||||
.propfind_with_headers(
|
||||
&test_base_path,
|
||||
ALL_DAV_PROPERTIES,
|
||||
[("prefer", "return=minimal")],
|
||||
)
|
||||
.await;
|
||||
response
|
||||
.properties(&test_base_path)
|
||||
.is_defined(DavProperty::WebDav(WebDavProperty::GetETag))
|
||||
.is_undefined(DavProperty::Principal(PrincipalProperty::GroupMembership));
|
||||
|
||||
// Test 9: Retrieve all static properties
|
||||
for (path, etag, is_file) in [
|
||||
(&test_base_path, &etag_folder, false),
|
||||
(&test_path, &etag_file, true),
|
||||
@@ -370,11 +426,34 @@ pub async fn test(test: &WebDavTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test 10: expand-property report
|
||||
for path in [&test_base_path, &test_path] {
|
||||
let response = client
|
||||
.request("REPORT", path, EXPAND_REPORT_QUERY)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None);
|
||||
let properties = response.properties(path);
|
||||
for prop in [
|
||||
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
|
||||
DavProperty::WebDav(WebDavProperty::Owner),
|
||||
] {
|
||||
properties.get(prop).with_some_values([
|
||||
format!(
|
||||
"D:response.D:href:{}/jane/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
)
|
||||
.as_str(),
|
||||
"D:response.D:propstat.D:prop.D:displayname:Jane Doe-Smith",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
for (path, etag, is_file) in [
|
||||
(&test_base_path, &etag_folder, false),
|
||||
(&test_path, &etag_file, true),
|
||||
] {
|
||||
// Test 9: PROPPATCH should fail when a precondition fails
|
||||
// Test 11: PROPPATCH should fail when a precondition fails
|
||||
client
|
||||
.proppatch(
|
||||
path,
|
||||
@@ -407,7 +486,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.with_status(StatusCode::OK)
|
||||
.without_values([etag.as_str()]);
|
||||
|
||||
// Test 10: PROPPATCH set on DAV properties
|
||||
// Test 12: PROPPATCH set on DAV properties
|
||||
client
|
||||
.patch_and_check(
|
||||
path,
|
||||
@@ -431,7 +510,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
)
|
||||
.await;
|
||||
|
||||
// Test 11: PROPPATCH remove on DAV properties
|
||||
// Test 13: PROPPATCH remove on DAV properties
|
||||
let mut props = vec![
|
||||
(
|
||||
DavProperty::DeadProperty(DeadElementTag::new(
|
||||
@@ -450,7 +529,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
|
||||
match resource_type {
|
||||
DavResourceName::File if is_file => {
|
||||
// Test 12: Change a file's content-type
|
||||
// Test 14: Change a file's content-type
|
||||
client
|
||||
.patch_and_check(
|
||||
path,
|
||||
@@ -462,7 +541,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await;
|
||||
}
|
||||
DavResourceName::Cal if !is_file => {
|
||||
// Test 13: Change a calendar's properties
|
||||
// Test 15: Change a calendar's properties
|
||||
client
|
||||
.patch_and_check(
|
||||
path,
|
||||
@@ -498,7 +577,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.await;
|
||||
}
|
||||
DavResourceName::Card if !is_file => {
|
||||
// Test 14: Change an addressbook's properties
|
||||
// Test 16: Change an addressbook's properties
|
||||
client
|
||||
.patch_and_check(
|
||||
path,
|
||||
@@ -521,7 +600,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Test 15: PROPPATCH should fail on large properties
|
||||
// Test 17: PROPPATCH should fail on large properties
|
||||
let mut chunky_props = vec![
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName),
|
||||
DavProperty::DeadProperty(DeadElementTag::new(
|
||||
@@ -572,7 +651,7 @@ pub async fn test(test: &WebDavTest) {
|
||||
.with_description("Property value is too long");
|
||||
}
|
||||
|
||||
// Test 16: PROPPATCH should fail on invalid calendar property values
|
||||
// Test 18: PROPPATCH should fail on invalid calendar property values
|
||||
if !is_file && resource_type == DavResourceName::Cal {
|
||||
let response = client
|
||||
.proppatch(
|
||||
@@ -603,7 +682,16 @@ pub async fn test(test: &WebDavTest) {
|
||||
.with_description("Invalid calendar timezone");
|
||||
}
|
||||
}
|
||||
|
||||
client
|
||||
.request("DELETE", &test_base_path, "")
|
||||
.await
|
||||
.with_status(StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
client.delete_default_containers().await;
|
||||
client.delete_default_containers_by_account("support").await;
|
||||
test.assert_is_empty().await;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -637,6 +725,16 @@ impl DavMultiStatus {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_hrefs<'x>(&self, expect_hrefs: impl IntoIterator<Item = &'x str>) -> &Self {
|
||||
let expect_hrefs: AHashSet<_> = expect_hrefs.into_iter().collect();
|
||||
let hrefs: AHashSet<_> = self.hrefs.keys().map(|s| s.as_str()).collect();
|
||||
if hrefs != expect_hrefs {
|
||||
self.response.dump_response();
|
||||
panic!("Expected hrefs {expect_hrefs:?}, but got {hrefs:?}",);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DavPropertyResult<'x> {
|
||||
@@ -671,6 +769,33 @@ impl DavPropertyResult<'_> {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_defined(&self, name: impl AsRef<str>) -> &Self {
|
||||
if self
|
||||
.properties
|
||||
.0
|
||||
.iter()
|
||||
.any(|prop| prop.values.contains_key(name.as_ref()))
|
||||
{
|
||||
self
|
||||
} else {
|
||||
self.response.dump_response();
|
||||
panic!("Expected property {} to be defined", name.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_undefined(&self, name: impl AsRef<str>) -> &Self {
|
||||
if self
|
||||
.properties
|
||||
.0
|
||||
.iter()
|
||||
.any(|prop| prop.values.contains_key(name.as_ref()))
|
||||
{
|
||||
self.response.dump_response();
|
||||
panic!("Expected property {} to be undefined", name.as_ref());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> DavQueryResult<'x> {
|
||||
@@ -689,6 +814,23 @@ impl<'x> DavQueryResult<'x> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_values(&self, expected_values: impl IntoIterator<Item = &'x str>) -> &Self {
|
||||
let values = self
|
||||
.values
|
||||
.iter()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<AHashSet<_>>();
|
||||
|
||||
for expected_value in expected_values {
|
||||
if !values.contains(expected_value) {
|
||||
self.response.dump_response();
|
||||
panic!("Expected at least one of {expected_value:?} values, but got {values:?}",);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn without_values(&self, expected_values: impl IntoIterator<Item = &'x str>) -> &Self {
|
||||
let expected_values = AHashSet::from_iter(expected_values);
|
||||
let values = self
|
||||
@@ -895,6 +1037,19 @@ impl DummyWebDavClient {
|
||||
}
|
||||
|
||||
pub async fn propfind<I, T>(&self, path: &str, properties: I) -> DavMultiStatus
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: AsRef<str>,
|
||||
{
|
||||
self.propfind_with_headers(path, properties, []).await
|
||||
}
|
||||
|
||||
pub async fn propfind_with_headers<I, T>(
|
||||
&self,
|
||||
path: &str,
|
||||
properties: I,
|
||||
headers: impl IntoIterator<Item = (&'static str, &str)>,
|
||||
) -> DavMultiStatus
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: AsRef<str>,
|
||||
@@ -913,7 +1068,7 @@ impl DummyWebDavClient {
|
||||
|
||||
request.push_str("</D:prop></D:propfind>");
|
||||
|
||||
self.request("PROPFIND", path, &request)
|
||||
self.request_with_headers("PROPFIND", path, headers, &request)
|
||||
.await
|
||||
.with_status(StatusCode::MULTI_STATUS)
|
||||
.into_propfind_response(None)
|
||||
@@ -975,7 +1130,21 @@ impl Default for DavItem {
|
||||
}
|
||||
}
|
||||
|
||||
const ALL_DAV_PROPERTIES: &[DavProperty] = &[
|
||||
const EXPAND_REPORT_QUERY: &str = r#"<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:expand-property xmlns:D="DAV:"
|
||||
xmlns:A="urn:ietf:params:xml:ns:caldav"
|
||||
xmlns:B="urn:ietf:params:xml:ns:carddav">
|
||||
<A:property name="calendar-description"/>
|
||||
<B:property name="addressbook-description"/>
|
||||
<D:property name="current-user-principal">
|
||||
<D:property name="displayname"/>
|
||||
</D:property>
|
||||
<D:property name="owner">
|
||||
<D:property name="displayname"/>
|
||||
</D:property>
|
||||
</D:expand-property>"#;
|
||||
|
||||
pub const ALL_DAV_PROPERTIES: &[DavProperty] = &[
|
||||
DavProperty::WebDav(WebDavProperty::CreationDate),
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName),
|
||||
DavProperty::WebDav(WebDavProperty::GetContentLanguage),
|
||||
|
||||
Reference in New Issue
Block a user