Registry testing - part 12

This commit is contained in:
Maurus Decimus
2026-03-22 17:17:11 +01:00
parent 21c541e505
commit 944505a925
101 changed files with 2114 additions and 2823 deletions

View File

@@ -4,17 +4,14 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
use dav_proto::schema::property::{DavProperty, WebDavProperty};
use groupware::DavResourceName;
use hyper::StatusCode;
use crate::webdav::GenerateTestDavResource;
use super::{DavResponse, DummyWebDavClient, WebDavTest};
pub async fn test(test: &WebDavTest) {
let owner_client = test.client("bill");
let sharee_client = test.client("john");
pub async fn test(test: &TestServer) {
let owner_client = test.account("bill@example.com").webdav_client();
let sharee_client = test.account("john@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
@@ -23,10 +20,16 @@ pub async fn test(test: &WebDavTest) {
] {
println!("Running ACL tests ({})...", resource_type.base_path());
let is_file = resource_type == DavResourceName::File;
let sharee_principal = format!("{}/john/", DavResourceName::Principal.base_path());
let sharee_base_path = format!("{}/john/", resource_type.base_path());
let owner_principal = format!("{}/bill/", DavResourceName::Principal.base_path());
let owner_base_path = format!("{}/bill/", resource_type.base_path());
let sharee_principal = format!(
"{}/john%40example.com/",
DavResourceName::Principal.base_path()
);
let sharee_base_path = format!("{}/john%40example.com/", resource_type.base_path());
let owner_principal = format!(
"{}/bill%40example.com/",
DavResourceName::Principal.base_path()
);
let owner_base_path = format!("{}/bill%40example.com/", resource_type.base_path());
// Create a resource for the owner
let owner_folder = format!("{owner_base_path}test-shared/");

View File

@@ -4,15 +4,14 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::utils::server::TestServer;
use dav_proto::Depth;
use hyper::StatusCode;
use super::WebDavTest;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running basic tests...");
let john = test.client("john");
let jane = test.client("jane");
let john = test.account("john@example.com").webdav_client();
let jane = test.account("jane@example.com").webdav_client();
// Test OPTIONS request
john.request("OPTIONS", "/dav/file", "")
@@ -37,18 +36,22 @@ pub async fn test(test: &WebDavTest) {
.await
.with_values(
"D:multistatus.D:response.D:href",
["/dav/card/", "/dav/card/john/"],
["/dav/card/", "/dav/card/john%40example.com/"],
);
jane.request("PROPFIND", "/.well-known/caldav", "")
.await
.with_values(
"D:multistatus.D:response.D:href",
["/dav/cal/", "/dav/cal/jane/", "/dav/cal/support/"],
[
"/dav/cal/",
"/dav/cal/jane%40example.com/",
"/dav/cal/support%40example.com/",
],
);
// Test 404 responses
jane.sync_collection(
"/dav/cal/jane/default/",
"/dav/cal/jane%40example.com/default/",
"",
Depth::Infinity,
None,
@@ -56,19 +59,19 @@ pub async fn test(test: &WebDavTest) {
)
.await;
jane.sync_collection(
"/dav/cal/jane/test-404/",
"/dav/cal/jane%40example.com/test-404/",
"",
Depth::Infinity,
None,
["D:getetag"],
)
.await;
jane.request("PROPFIND", "/dav/cal/jane/default/", "")
jane.request("PROPFIND", "/dav/cal/jane%40example.com/default/", "")
.await
.with_status(StatusCode::MULTI_STATUS);
jane.request(
"REPORT",
"/dav/cal/jane/default/",
"/dav/cal/jane%40example.com/default/",
concat!(
r#"<CAL:calendar-query xmlns="DAV:" "#,
r#"xmlns:CAL="urn:ietf:params:xml:ns:caldav"><prop><getetag />"#,
@@ -81,7 +84,7 @@ pub async fn test(test: &WebDavTest) {
.with_status(StatusCode::MULTI_STATUS);
jane.request(
"REPORT",
"/dav/cal/jane/test-404/",
"/dav/cal/jane%40example.com/test-404/",
concat!(
r#"<CAL:calendar-query xmlns="DAV:" "#,
r#"xmlns:CAL="urn:ietf:params:xml:ns:caldav"><prop><getetag />"#,
@@ -92,12 +95,13 @@ pub async fn test(test: &WebDavTest) {
)
.await
.with_status(StatusCode::MULTI_STATUS);
jane.request("PROPFIND", "/dav/cal/jane/test-404/", "")
jane.request("PROPFIND", "/dav/cal/jane%40example.com/test-404/", "")
.await
.with_status(StatusCode::NOT_FOUND);
john.delete_default_containers().await;
jane.delete_default_containers().await;
jane.delete_default_containers_by_account("support").await;
jane.delete_default_containers_by_account("support@example.com")
.await;
test.assert_is_empty().await;
}

View File

@@ -4,20 +4,20 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::jmap::mail::mailbox::destroy_all_mailboxes_for_account;
use crate::utils::server::TestServer;
use email::cache::MessageCacheFetch;
use hyper::StatusCode;
use mail_parser::{DateTime, MessageParser};
use store::write::now;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running calendar e-mail alarms tests...");
let client = test.client("john");
let account = test.account("john@example.com");
let client = account.webdav_client();
client
.request_with_headers(
"PUT",
"/dav/cal/john/default/its-alarming-how-charming-i-feel.ics",
"/dav/cal/john%40example.com/default/its-alarming-how-charming-i-feel.ics",
[("content-type", "text/calendar; charset=utf-8")],
TEST_ALARM_1.replace(
"$START",
@@ -74,7 +74,7 @@ pub async fn test(test: &WebDavTest) {
}
assert!(
contents.contains(concat!(
"/dav/cal/john/default/",
"/dav/cal/john%40example.com/default/",
"its-alarming-how-charming-i-feel.ics"
)),
"failed for {contents}"
@@ -82,7 +82,7 @@ pub async fn test(test: &WebDavTest) {
}
client.delete_default_containers().await;
destroy_all_mailboxes_for_account(client.account_id).await;
test.destroy_all_mailboxes(account).await;
test.assert_is_empty().await
}

View File

@@ -4,7 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::utils::server::TestServer;
use ahash::AHashSet;
use calcard::{common::timezone::Tz, icalendar::ICalendar};
use groupware::{
@@ -15,10 +16,10 @@ use hyper::StatusCode;
use store::write::serialize::rkyv_unarchive;
use types::TimeRange;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running REPORT calendar-query & free-busy-query tests...");
let client = test.client("john");
let cal_path = format!("{}/john/default/", DavResourceName::Cal.base_path());
let client = test.account("john@example.com").webdav_client();
let cal_path = format!("{}/john%40example.com/default/", DavResourceName::Cal.base_path());
#[allow(clippy::never_loop)]
for (num, ics) in [
@@ -324,7 +325,7 @@ fn roundtrip_expansion(ics: &str, ignore_errors: bool) {
fn rfc_file_name(num: usize) -> String {
format!(
"{}/john/default/abcd{num}.ics",
"{}/john%40example.com/default/abcd{num}.ics",
DavResourceName::Cal.base_path()
)
}

View File

@@ -4,11 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::{
jmap::mail::mailbox::destroy_all_mailboxes_for_account,
webdav::{DummyWebDavClient, prop::ALL_DAV_PROPERTIES},
utils::{server::TestServer, webdav::DummyWebDavClient},
webdav::prop::ALL_DAV_PROPERTIES,
};
use calcard::{
common::timezone::Tz,
icalendar::{
@@ -16,7 +16,6 @@ use calcard::{
ICalendarProperty, ICalendarRecurrenceRule, ICalendarWeekday,
},
};
use common::{Server, auth::AccessToken};
use dav_proto::schema::property::{CalDavProperty, DavProperty, WebDavProperty};
use email::cache::MessageCacheFetch;
use groupware::{
@@ -30,23 +29,30 @@ use std::str::FromStr;
use store::write::now;
use types::collection::SyncCollection;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running calendar scheduling tests...");
let bill_client = test.client("bill");
let jane_client = test.client("jane");
let john_client = test.client("john");
let bill = test.account("bill@example.com");
let jane = test.account("jane@example.com");
let john = test.account("john@example.com");
let bill_client = bill.webdav_client();
let jane_client = jane.webdav_client();
let john_client = john.webdav_client();
// Validate hierarchy of scheduling resources
let response = jane_client
.propfind_with_headers("/dav/itip/jane/", ALL_DAV_PROPERTIES, [("depth", "1")])
.propfind_with_headers(
"/dav/itip/jane%40example.com/",
ALL_DAV_PROPERTIES,
[("depth", "1")],
)
.await;
let properties = response
.with_hrefs([
"/dav/itip/jane/",
"/dav/itip/jane/inbox/",
"/dav/itip/jane/outbox/",
"/dav/itip/jane%40example.com/",
"/dav/itip/jane%40example.com/inbox/",
"/dav/itip/jane%40example.com/outbox/",
])
.properties("/dav/itip/jane/inbox/");
.properties("/dav/itip/jane%40example.com/inbox/");
// Validate schedule inbox properties
properties
@@ -56,7 +62,7 @@ pub async fn test(test: &WebDavTest) {
.get(DavProperty::CalDav(
CalDavProperty::ScheduleDefaultCalendarURL,
))
.with_values(["D:href:/dav/cal/jane/default/"])
.with_values(["D:href:/dav/cal/jane%40example.com/default/"])
.with_status(StatusCode::OK);
properties
.get(DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet))
@@ -101,7 +107,7 @@ pub async fn test(test: &WebDavTest) {
]);
// Validate schedule outbox properties
let properties = response.properties("/dav/itip/jane/outbox/");
let properties = response.properties("/dav/itip/jane%40example.com/outbox/");
properties
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
.with_values(["D:collection", "A:schedule-outbox"]);
@@ -164,7 +170,7 @@ pub async fn test(test: &WebDavTest) {
john_client
.request_with_headers(
"PUT",
"/dav/cal/john/default/itip.ics",
"/dav/cal/john%40example.com/default/itip.ics",
[("content-type", "text/calendar; charset=utf-8")],
&test_itip,
)
@@ -174,28 +180,27 @@ pub async fn test(test: &WebDavTest) {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
// Check that the invitation was received by Bill and Mike
for client in [bill_client, jane_client] {
for client in [&bill_client, &jane_client] {
let messages = test
.server
.get_cached_messages(client.account_id)
.await
.unwrap();
assert_eq!(messages.emails.items.len(), 1);
let access_token = test
.server
.get_access_token(client.account_id)
.await
.unwrap();
let events = test
.server
.fetch_dav_resources(&access_token, client.account_id, SyncCollection::Calendar)
.fetch_dav_resources(
client.account_id,
client.account_id,
SyncCollection::Calendar,
)
.await
.unwrap();
assert_eq!(events.resources.len(), 2);
let events = test
.server
.fetch_dav_resources(
&access_token,
client.account_id,
client.account_id,
SyncCollection::CalendarEventNotification,
)
@@ -205,7 +210,7 @@ pub async fn test(test: &WebDavTest) {
}
// Validate iTIP
let itips = fetch_and_remove_itips(jane_client).await;
let itips = jane_client.fetch_and_remove_itips().await;
assert_eq!(itips.len(), 1);
let itip = itips.first().unwrap();
assert!(
@@ -214,7 +219,7 @@ pub async fn test(test: &WebDavTest) {
);
// Fetch added calendar entry
let cals = fetch_icals(jane_client).await;
let cals = jane_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
let cal = cals.into_iter().next().unwrap();
@@ -252,14 +257,14 @@ pub async fn test(test: &WebDavTest) {
// Make sure that the schedule has not changed
assert_eq!(
fetch_icals(jane_client).await[0].schedule_tag,
jane_client.fetch_icals().await[0].schedule_tag,
cal.schedule_tag
);
// Check that John received the RSVP
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
test.wait_for_tasks().await;
let itips = fetch_and_remove_itips(john_client).await;
let itips = john_client.fetch_and_remove_itips().await;
assert_eq!(itips.len(), 1);
assert!(
itips[0].contains("METHOD:REPLY")
@@ -267,7 +272,7 @@ pub async fn test(test: &WebDavTest) {
"failed for itip: {}",
itips[0]
);
let cals = fetch_icals(john_client).await;
let cals = john_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
assert!(
cals[0]
@@ -290,7 +295,7 @@ pub async fn test(test: &WebDavTest) {
.with_status(StatusCode::NO_CONTENT);
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert_eq!(
fetch_and_remove_itips(john_client).await,
john_client.fetch_and_remove_itips().await,
Vec::<String>::new()
);
@@ -300,7 +305,7 @@ pub async fn test(test: &WebDavTest) {
.await
.with_status(StatusCode::NO_CONTENT);
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let itips = fetch_and_remove_itips(john_client).await;
let itips = john_client.fetch_and_remove_itips().await;
assert_eq!(itips.len(), 1);
assert!(
itips[0].contains("METHOD:REPLY")
@@ -308,7 +313,7 @@ pub async fn test(test: &WebDavTest) {
"failed for itip: {}",
itips[0]
);
let cals = fetch_icals(john_client).await;
let cals = john_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
let cal = cals.into_iter().next().unwrap();
assert!(
@@ -358,7 +363,7 @@ pub async fn test(test: &WebDavTest) {
response.contains("Lunch") && response.contains("RSVP has been recorded"),
"failed for response: {response}"
);
let cals = fetch_icals(john_client).await;
let cals = john_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
let cal = cals.into_iter().next().unwrap();
assert!(
@@ -384,7 +389,7 @@ pub async fn test(test: &WebDavTest) {
let response = john_client
.request_with_headers(
"POST",
"/dav/itip/john/outbox/",
"/dav/itip/john%40example.com/outbox/",
[("content-type", "text/calendar; charset=utf-8")],
&test_outbox,
)
@@ -442,7 +447,7 @@ pub async fn test(test: &WebDavTest) {
// Make sure that the schedule has changed
assert_ne!(
fetch_icals(john_client).await[0].schedule_tag,
john_client.fetch_icals().await[0].schedule_tag,
cal.schedule_tag
);
let main_event_href = cal.href;
@@ -450,7 +455,7 @@ pub async fn test(test: &WebDavTest) {
// Check that Bill received the update
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
test.wait_for_tasks().await;
let mut itips = fetch_and_remove_itips(bill_client).await;
let mut itips = bill_client.fetch_and_remove_itips().await;
itips.sort_unstable_by(|a, _| {
if a.contains("Lunch") {
std::cmp::Ordering::Less
@@ -469,7 +474,7 @@ pub async fn test(test: &WebDavTest) {
"failed for itip: {}",
itips[1]
);
let cals = fetch_icals(bill_client).await;
let cals = bill_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
let cal = cals.into_iter().next().unwrap();
assert!(
@@ -480,7 +485,7 @@ pub async fn test(test: &WebDavTest) {
);
let attendee_href = cal.href;
assert_eq!(
fetch_and_remove_itips(jane_client).await,
jane_client.fetch_and_remove_itips().await,
Vec::<String>::new()
);
@@ -490,14 +495,14 @@ pub async fn test(test: &WebDavTest) {
.await
.with_status(StatusCode::NO_CONTENT);
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let itips = fetch_and_remove_itips(bill_client).await;
let itips = bill_client.fetch_and_remove_itips().await;
assert_eq!(itips.len(), 1);
assert!(
itips[0].contains("METHOD:CANCEL") && itips[0].contains("STATUS:CANCELLED"),
"failed for itip: {}",
itips[0]
);
let cals = fetch_icals(bill_client).await;
let cals = bill_client.fetch_icals().await;
assert_eq!(cals.len(), 1);
let cal = cals.into_iter().next().unwrap();
assert!(
@@ -506,7 +511,7 @@ pub async fn test(test: &WebDavTest) {
cal.ical
);
assert_eq!(
fetch_and_remove_itips(jane_client).await,
jane_client.fetch_and_remove_itips().await,
Vec::<String>::new()
);
@@ -517,40 +522,43 @@ pub async fn test(test: &WebDavTest) {
.with_status(StatusCode::NO_CONTENT);
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
assert_eq!(
fetch_and_remove_itips(john_client).await,
john_client.fetch_and_remove_itips().await,
Vec::<String>::new()
);
for client in [bill_client, jane_client, john_client] {
client.delete_default_containers().await;
destroy_all_mailboxes_for_account(client.account_id).await;
}
for account in [bill, jane, john] {
test.destroy_all_mailboxes(account).await;
}
test.assert_is_empty().await;
}
async fn fetch_and_remove_itips(client: &DummyWebDavClient) -> Vec<String> {
let inbox_href = format!("/dav/itip/{}/inbox/", client.name);
let response = client
.propfind_with_headers(&inbox_href, ALL_DAV_PROPERTIES, [("depth", "1")])
.await;
let mut itips = vec![];
impl DummyWebDavClient {
async fn fetch_and_remove_itips(&self) -> Vec<String> {
let inbox_href = format!("/dav/itip/{}/inbox/", self.name.replace('@', "%40"));
let response = self
.propfind_with_headers(&inbox_href, ALL_DAV_PROPERTIES, [("depth", "1")])
.await;
let mut itips = vec![];
for href in response.hrefs.keys().filter(|&href| href != &inbox_href) {
let itip = client
.request("GET", href, "")
.await
.with_status(StatusCode::OK)
.body
.expect("Missing body");
client
.request("DELETE", href, "")
.await
.with_status(StatusCode::NO_CONTENT);
itips.push(itip);
for href in response.hrefs.keys().filter(|&href| href != &inbox_href) {
let itip = self
.request("GET", href, "")
.await
.with_status(StatusCode::OK)
.body
.expect("Missing body");
self.request("DELETE", href, "")
.await
.with_status(StatusCode::NO_CONTENT);
itips.push(itip);
}
itips
}
itips
}
#[derive(Debug)]
@@ -560,42 +568,46 @@ struct CalEntry {
schedule_tag: String,
}
async fn fetch_icals(client: &DummyWebDavClient) -> Vec<CalEntry> {
let cal_inbox = format!("/dav/cal/{}/default/", client.name);
let response = client
.propfind_with_headers(&cal_inbox, ALL_DAV_PROPERTIES, [("depth", "1")])
.await;
let mut cals = vec![];
impl DummyWebDavClient {
async fn fetch_icals(&self) -> Vec<CalEntry> {
let cal_inbox = format!("/dav/cal/{}/default/", self.name.replace('@', "%40"));
let response = self
.propfind_with_headers(&cal_inbox, ALL_DAV_PROPERTIES, [("depth", "1")])
.await;
let mut cals = vec![];
for href in response.hrefs.keys().filter(|&href| href != &cal_inbox) {
let ical = client
.request("GET", href, "")
.await
.with_status(StatusCode::OK)
.body
.expect("Missing body");
let properties = response.properties(href);
for href in response.hrefs.keys().filter(|&href| href != &cal_inbox) {
let ical = self
.request("GET", href, "")
.await
.with_status(StatusCode::OK)
.body
.expect("Missing body");
let properties = response.properties(href);
assert!(
!ical.contains("METHOD:"),
"iTIP method found in calendar entry: {ical}"
);
assert!(
!ical.contains("METHOD:"),
"iTIP method found in calendar entry: {ical}"
);
cals.push(CalEntry {
href: href.to_string(),
ical,
schedule_tag: properties
.get(DavProperty::CalDav(CalDavProperty::ScheduleTag))
.value()
.to_string(),
});
cals.push(CalEntry {
href: href.to_string(),
ical,
schedule_tag: properties
.get(DavProperty::CalDav(CalDavProperty::ScheduleTag))
.value()
.to_string(),
});
}
cals
}
cals
}
pub async fn test_build_itip_templates(server: &Server) {
let dummy_access_token = AccessToken::from_id(0);
pub async fn test_build_itip_templates(test: &TestServer) {
let account = test.account("john@example.com");
let account_id = account.id().document_id();
let account_info = test.server.account_info(account_id).await.unwrap();
for (idx, summary) in [
ItipSummary::Invite(vec![
@@ -784,19 +796,14 @@ pub async fn test_build_itip_templates(server: &Server) {
.into_iter()
.enumerate()
{
let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&summary)
.unwrap()
.to_vec();
let summary = rkyv::access::<ArchivedItipSummary, rkyv::rancor::Error>(&bytes).unwrap();
let html = build_itip_template(
server,
&dummy_access_token,
0,
&test.server,
&account_info,
account_id,
1,
"john.doe@example.org",
"jane.smith@example.net",
summary,
&summary,
"124",
)
.await;

View File

@@ -4,17 +4,18 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::utils::server::TestServer;
use dav_proto::schema::property::{CardDavProperty, DavProperty, WebDavProperty};
use groupware::DavResourceName;
use hyper::StatusCode;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running REPORT addressbook-query tests...");
let client = test.client("john");
let client = test.account("john@example.com").webdav_client();
// Create test data
let default_path = format!("{}/john/default/", DavResourceName::Card.base_path());
let default_path = format!("{}/john%40example.com/default/", DavResourceName::Card.base_path());
let mut hrefs = Vec::with_capacity(3);
for (i, vcard) in [VCARD1, VCARD2, VCARD3].iter().enumerate() {
let href = format!("{default_path}contact-{i}.vcf",);

View File

@@ -4,16 +4,20 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{DavResponse, WebDavTest};
use crate::webdav::GenerateTestDavResource;
use crate::utils::{
server::TestServer,
webdav::{DavResponse, GenerateTestDavResource},
};
use ahash::AHashSet;
use dav_proto::Depth;
use groupware::DavResourceName;
use hyper::StatusCode;
use registry::schema::structs::Action;
pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
let client = test.client("jane");
let mike_noquota = test.client("mike");
pub async fn test(test: &TestServer, assisted_discovery: bool) {
let admin = test.account("admin@example.com");
let client = test.account("jane@example.com").webdav_client();
let mike_noquota = test.account("mike@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
@@ -21,8 +25,8 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
DavResourceName::Card,
] {
println!("Running COPY/MOVE tests ({})...", resource_type.base_path());
let user_base_path = format!("{}/jane", resource_type.base_path());
let group_base_path = format!("{}/support", resource_type.base_path());
let user_base_path = format!("{}/jane%40example.com", resource_type.base_path());
let group_base_path = format!("{}/support%40example.com", resource_type.base_path());
let default_test_depth = if resource_type == DavResourceName::File {
2
} else {
@@ -73,7 +77,7 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
client.validate_values(&hierarchy).await;
// Delete cache an resync
test.clear_cache();
admin.registry_create_object(Action::InvalidateCaches).await;
let response = client
.sync_collection(
&user_base_path,
@@ -724,7 +728,10 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
// Test 19: Quota enforcement (on CalDAV/CardDAV items are linked, not copied therefore there is no quota increase)
if resource_type == DavResourceName::File {
let path = format!("{}/mike/quota-test/", resource_type.base_path());
let path = format!(
"{}/mike%40example.com/quota-test/",
resource_type.base_path()
);
let content = resource_type.generate();
mike_noquota
.mkcol("MKCOL", &path, [], [])
@@ -744,7 +751,11 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
&path,
[(
"destination",
format!("{}/mike/quota-test{i}", resource_type.base_path()).as_str(),
format!(
"{}/mike%40example.com/quota-test{i}",
resource_type.base_path()
)
.as_str(),
)],
&content,
)
@@ -775,7 +786,10 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
mike_noquota
.request(
"DELETE",
&format!("{}/mike/quota-test{i}", resource_type.base_path()),
&format!(
"{}/mike%40example.com/quota-test{i}",
resource_type.base_path()
),
"",
)
.await
@@ -785,7 +799,9 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
}
client.delete_default_containers().await;
client.delete_default_containers_by_account("support").await;
client
.delete_default_containers_by_account("support@example.com")
.await;
mike_noquota.delete_default_containers().await;
test.assert_is_empty().await;
}
@@ -796,7 +812,9 @@ fn assert_result(response: &DavResponse, hierarchy: &[(String, String)]) {
.hrefs()
.into_iter()
.filter(|h| {
!h.ends_with("/jane/") && !h.ends_with("/support/") && !h.ends_with("/default/")
!h.ends_with("/jane%40example.com/")
&& !h.ends_with("/support%40example.com/")
&& !h.ends_with("/default/")
})
.collect::<AHashSet<_>>();
let hierarchy = hierarchy

View File

@@ -4,14 +4,14 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{DavResponse, DummyWebDavClient, WebDavTest};
use crate::webdav::GenerateTestDavResource;
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
use dav_proto::schema::property::{DavProperty, WebDavProperty};
use groupware::DavResourceName;
use hyper::StatusCode;
pub async fn test(test: &WebDavTest) {
let client = test.client("john");
pub async fn test(test: &TestServer) {
let client = test.account("john@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
@@ -22,7 +22,7 @@ pub async fn test(test: &WebDavTest) {
"Running LOCK/UNLOCK tests ({})...",
resource_type.base_path()
);
let base_path = format!("{}/john", resource_type.base_path());
let base_path = format!("{}/john%40example.com", resource_type.base_path());
// Test 1: Creating a collection under an unmapped resource without providing a lock token should fail
let path = format!("{base_path}/do-not-write");
@@ -201,9 +201,3 @@ pub async fn test(test: &WebDavTest) {
client.delete_default_containers().await;
test.assert_is_empty().await;
}
impl DavResponse {
pub fn lock_token(&self) -> &str {
self.value("D:prop.D:lockdiscovery.D:activelock.D:locktoken.D:href")
}
}

View File

@@ -8,11 +8,12 @@ use hyper::StatusCode;
use crate::webdav::{TEST_FILE_1, TEST_ICAL_1, TEST_VCARD_1, TEST_VTIMEZONE_1};
use super::{DavResponse, DummyWebDavClient, WebDavTest};
use crate::utils::server::TestServer;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running MKCOL tests...");
let client = test.client("john");
let client = test.account("john@example.com").webdav_client();
// Creating collections in root elements is not allowed
for path in [
@@ -29,9 +30,9 @@ pub async fn test(test: &WebDavTest) {
// Create collections using MKCOL (empty body)
for path in [
"/dav/file/john/my-files",
"/dav/card/john/my-cards",
"/dav/cal/john/my-events",
"/dav/file/john%40example.com/my-files",
"/dav/card/john%40example.com/my-cards",
"/dav/cal/john%40example.com/my-events",
] {
client
.request("MKCOL", path, "")
@@ -41,9 +42,9 @@ pub async fn test(test: &WebDavTest) {
// Create resources under the newly created collections
for (path, content) in [
("/dav/file/john/my-files/file1.txt", TEST_FILE_1),
("/dav/card/john/my-cards/card1.vcf", TEST_VCARD_1),
("/dav/cal/john/my-events/event1.ics", TEST_ICAL_1),
("/dav/file/john%40example.com/my-files/file1.txt", TEST_FILE_1),
("/dav/card/john%40example.com/my-cards/card1.vcf", TEST_VCARD_1),
("/dav/cal/john%40example.com/my-events/event1.ics", TEST_ICAL_1),
] {
client
.request("PUT", path, content)
@@ -53,12 +54,12 @@ pub async fn test(test: &WebDavTest) {
// Creating a collection on a mapped resource should fail
for path in [
"/dav/file/john/my-files",
"/dav/card/john/my-cards",
"/dav/cal/john/my-events",
"/dav/file/john/my-files/file1.txt",
"/dav/card/john/my-cards/card1.vcf",
"/dav/cal/john/my-events/event1.ics",
"/dav/file/john%40example.com/my-files",
"/dav/card/john%40example.com/my-cards",
"/dav/cal/john%40example.com/my-events",
"/dav/file/john%40example.com/my-files/file1.txt",
"/dav/card/john%40example.com/my-cards/card1.vcf",
"/dav/cal/john%40example.com/my-events/event1.ics",
] {
client
.request("MKCOL", path, "")
@@ -68,13 +69,13 @@ pub async fn test(test: &WebDavTest) {
// Creating a sub-collections is allowed in FileDAV but in CalDAV and CardDAV
for (path, expected_status) in [
("/dav/file/john/my-files/my-sub-files", StatusCode::CREATED),
("/dav/file/john%40example.com/my-files/my-sub-files", StatusCode::CREATED),
(
"/dav/card/john/my-cards/my-sub-cards",
"/dav/card/john%40example.com/my-cards/my-sub-cards",
StatusCode::METHOD_NOT_ALLOWED,
),
(
"/dav/cal/john/my-events/my-sub-events",
"/dav/cal/john%40example.com/my-events/my-sub-events",
StatusCode::METHOD_NOT_ALLOWED,
),
] {
@@ -86,9 +87,9 @@ pub async fn test(test: &WebDavTest) {
// Extended MKCOL with an unsupported resource types should fail
for (path, resource_type) in [
("/dav/file/john/my-named-files", "B:addressbook"),
("/dav/card/john/my-named-cards", "A:calendar"),
("/dav/cal/john/my-named-events", "B:addressbook"),
("/dav/file/john%40example.com/my-named-files", "B:addressbook"),
("/dav/card/john%40example.com/my-named-cards", "A:calendar"),
("/dav/cal/john%40example.com/my-named-events", "B:addressbook"),
] {
client
.mkcol("MKCOL", path, ["D:collection", resource_type], [])
@@ -104,12 +105,12 @@ pub async fn test(test: &WebDavTest) {
// Create using extended MKCOL
for (path, expected_properties, resource_types) in [
(
"/dav/file/john/my-named-files/",
"/dav/file/john%40example.com/my-named-files/",
[("D:displayname", "Named Files")].as_slice(),
["D:collection"].as_slice(),
),
(
"/dav/card/john/my-named-cards/",
"/dav/card/john%40example.com/my-named-cards/",
[
("D:displayname", "Named Cards"),
("B:addressbook-description", "Some amazing contacts"),
@@ -118,7 +119,7 @@ pub async fn test(test: &WebDavTest) {
["D:collection", "B:addressbook"].as_slice(),
),
(
"/dav/cal/john/my-named-events/",
"/dav/cal/john%40example.com/my-named-events/",
[
("D:displayname", "Named Events"),
("A:calendar-description", "Some amazing events"),
@@ -166,7 +167,7 @@ pub async fn test(test: &WebDavTest) {
client
.mkcol(
"MKCALENDAR",
"/dav/cal/john/my-named-events2",
"/dav/cal/john%40example.com/my-named-events2",
[],
[
("D:displayname", "Named Events 2"),
@@ -183,7 +184,7 @@ pub async fn test(test: &WebDavTest) {
client
.mkcol(
"MKCALENDAR",
"/dav/cal/john/my-named-events3",
"/dav/cal/john%40example.com/my-named-events3",
[],
[
("D:displayname", "Named Events 3"),
@@ -203,11 +204,11 @@ pub async fn test(test: &WebDavTest) {
// Check the properties of the created calendars
client
.propfind(
"/dav/cal/john/my-named-events2/",
"/dav/cal/john%40example.com/my-named-events2/",
["A:supported-calendar-component-set"],
)
.await
.properties("/dav/cal/john/my-named-events2/")
.properties("/dav/cal/john%40example.com/my-named-events2/")
.get("A:supported-calendar-component-set")
.with_status(StatusCode::OK)
.with_values([
@@ -227,25 +228,25 @@ pub async fn test(test: &WebDavTest) {
]);
client
.propfind(
"/dav/cal/john/my-named-events3/",
"/dav/cal/john%40example.com/my-named-events3/",
["A:supported-calendar-component-set"],
)
.await
.properties("/dav/cal/john/my-named-events3/")
.properties("/dav/cal/john%40example.com/my-named-events3/")
.get("A:supported-calendar-component-set")
.with_status(StatusCode::OK)
.with_values(["A:comp.[name]:VEVENT", "A:comp.[name]:VTODO"]);
// Delete everything
for path in [
"/dav/file/john/my-files",
"/dav/card/john/my-cards",
"/dav/cal/john/my-events",
"/dav/file/john/my-named-files",
"/dav/card/john/my-named-cards",
"/dav/cal/john/my-named-events",
"/dav/cal/john/my-named-events2",
"/dav/cal/john/my-named-events3",
"/dav/file/john%40example.com/my-files",
"/dav/card/john%40example.com/my-cards",
"/dav/cal/john%40example.com/my-events",
"/dav/file/john%40example.com/my-named-files",
"/dav/card/john%40example.com/my-named-cards",
"/dav/cal/john%40example.com/my-named-events",
"/dav/cal/john%40example.com/my-named-events2",
"/dav/cal/john%40example.com/my-named-events3",
] {
client
.request("DELETE", path, "")
@@ -255,4 +256,3 @@ pub async fn test(test: &WebDavTest) {
client.delete_default_containers().await;
test.assert_is_empty().await;
}

View File

@@ -4,53 +4,25 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
AssertConfig, TEST_USERS, add_test_certs,
directory::internal::TestInternalDirectory,
jmap::{assert_is_empty, wait_for_tasks},
store::{
TempDir, build_store_config,
cleanup::{search_store_destroy, store_destroy},
use crate::utils::server::TestServerBuilder;
use ahash::AHashMap;
use common::{DavResource, DavResources};
use groupware::DavResourceName;
use hyper::StatusCode;
use registry::{
schema::{
enums::{Permission, StorageQuota},
prelude::{ObjectType, Property},
structs::{
CalendarAlarm, CalendarScheduling, Expression, MtaStageAuth, Sharing, SystemSettings,
WebDav,
},
},
types::EnumImpl,
};
use ::managesieve::core::ManageSieveSessionManager;
use ahash::{AHashMap, AHashSet};
use base64::{Engine, engine::general_purpose::STANDARD};
use common::{
Caches, Core, Data, DavResource, DavResources, Inner, Server,
config::{
server::{Listeners, ServerProtocol},
telemetry::Telemetry,
},
manager::boot::build_ipc,
};
use dav_proto::{
schema::property::{DavProperty, WebDavProperty},
xml_pretty_print,
};
use email::message::metadata::MessageMetadata;
use groupware::{DavResourceName, cache::GroupwareCache};
use http::HttpSessionManager;
use hyper::{HeaderMap, Method, StatusCode, header::AUTHORIZATION};
use imap::core::ImapSessionManager;
use pop3::Pop3SessionManager;
use quick_xml::Reader;
use quick_xml::events::Event;
use services::SpawnServices;
use smtp::{SpawnQueueManager, core::SmtpSessionManager};
use std::{borrow::Cow, str};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use store::{
ValueKey,
rand::{Rng, distr::Alphanumeric, rng},
write::{AlignedBytes, Archive},
};
use tokio::sync::watch;
use types::{collection::Collection, field::EmailField};
use utils::config::Config;
use serde_json::json;
use std::str;
use std::time::Instant;
pub mod acl;
pub mod basic;
@@ -70,8 +42,6 @@ pub mod sync;
#[test]
fn webdav_tests() {
//test_build_itip_templates(&handle.server).await;
tokio::runtime::Builder::new_multi_thread()
.thread_stack_size(8 * 1024 * 1024) // 8MB stack
.enable_all()
@@ -80,25 +50,156 @@ fn webdav_tests() {
.block_on(async {
// Prepare settings
let assisted_discovery = std::env::var("ASSISTED_DISCOVERY").unwrap_or_default() == "1";
let start_time = Instant::now();
let delete = true;
let handle = init_webdav_tests(assisted_discovery, delete).await;
basic::test(&handle).await;
put_get::test(&handle).await;
mkcol::test(&handle).await;
copy_move::test(&handle, assisted_discovery).await;
prop::test(&handle, assisted_discovery).await;
multiget::test(&handle).await;
sync::test(&handle).await;
lock::test(&handle).await;
principals::test(&handle, assisted_discovery).await;
acl::test(&handle).await;
card_query::test(&handle).await;
cal_query::test(&handle).await;
cal_alarm::test(&handle).await;
let mut test = TestServerBuilder::new("webdav_tests")
.await
.with_default_listeners()
.await
.build()
.await;
// Create admin account
let admin = test.create_admin_account("admin@example.com").await;
// Create test users
for (name, secret, description, aliases) in [
(
"john@example.com",
"secret2 + some more text",
"John Doe",
&["jdoe@example.com"],
),
(
"jane@example.com",
"secret3 + some more text",
"Jane Doe-Smith",
&["jane.smith@example.com"],
),
(
"bill@example.com",
"secret4 + some more text",
"Bill Foobar",
&["bill@example.com"],
),
(
"mike@example.com",
"secret5 + some more text",
"Mike Noquota",
&["mike@example.com"],
),
] {
let account = admin
.create_user_account(
name,
secret,
description,
aliases,
vec![
Permission::UnlimitedRequests,
Permission::UnlimitedUploads,
Permission::DavPrincipalList,
Permission::DavPrincipalSearch,
],
)
.await;
if name == "mike@example.com" {
admin
.registry_update_object(
ObjectType::Account,
account.id(),
json!({
Property::Quotas: { StorageQuota::MaxDiskQuota.as_str(): 1024}
}),
)
.await;
}
test.insert_account(account);
}
// Create test group
test.insert_account(
admin
.create_group_account("support@example.com", "Support Group", &[])
.await,
);
// Add Jane to the Support group
let support_id = test.account("support@example.com").id();
admin
.registry_update_object(
ObjectType::Account,
test.account("jane@example.com").id(),
json!({
"memberGroupIds": { support_id: true },
}),
)
.await;
// Add test settings
admin
.registry_update_setting(
SystemSettings {
default_hostname: "webdav.example.org".to_string(),
..Default::default()
},
&[Property::DefaultHostname],
)
.await;
admin
.registry_create_object(MtaStageAuth {
require: Expression {
else_: "false".to_string(),
..Default::default()
},
..Default::default()
})
.await;
admin
.registry_create_object(CalendarAlarm {
min_trigger_interval: 1000u64.into(),
..Default::default()
})
.await;
admin
.registry_create_object(Sharing {
allow_directory_queries: true,
..Default::default()
})
.await;
admin
.registry_create_object(CalendarScheduling {
auto_add_invitations: true,
..Default::default()
})
.await;
admin
.registry_create_object(WebDav {
enable_assisted_discovery: assisted_discovery,
..Default::default()
})
.await;
admin.reload_settings().await;
test.insert_account(admin);
let start_time = Instant::now();
//test_build_itip_templates(&test).await;
basic::test(&test).await;
put_get::test(&test).await;
mkcol::test(&test).await;
copy_move::test(&test, assisted_discovery).await;
prop::test(&test, assisted_discovery).await;
multiget::test(&test).await;
sync::test(&test).await;
lock::test(&test).await;
principals::test(&test, assisted_discovery).await;
acl::test(&test).await;
card_query::test(&test).await;
cal_query::test(&test).await;
cal_alarm::test(&test).await;
cal_itip::test();
cal_scheduling::test(&handle).await;
cal_scheduling::test(&test).await;
// Print elapsed time
let elapsed = start_time.elapsed();
@@ -109,179 +210,12 @@ fn webdav_tests() {
);
// Remove test data
if delete {
handle.temp_dir.delete();
if test.is_reset() {
test.temp_dir.delete();
}
});
}
#[allow(dead_code)]
pub struct WebDavTest {
server: Server,
clients: AHashMap<&'static str, DummyWebDavClient>,
temp_dir: TempDir,
shutdown_tx: watch::Sender<bool>,
}
async fn init_webdav_tests(assisted_discovery: bool, delete_if_exists: bool) -> WebDavTest {
// Load and parse config
let temp_dir = TempDir::new("webdav_tests", delete_if_exists);
let mut config = Config::new(
add_test_certs(&(build_store_config(&temp_dir.path.to_string_lossy()) + SERVER))
.replace("{TMP}", &temp_dir.path.display().to_string())
.replace("{ASSISTED_DISCOVERY}", &assisted_discovery.to_string())
.replace(
"{LEVEL}",
&std::env::var("LOG").unwrap_or_else(|_| "disable".to_string()),
),
)
.unwrap();
config.resolve_all_macros().await;
// Parse servers
let mut servers = Listeners::parse(&mut config);
// Bind ports and drop privileges
servers.bind_and_drop_priv(&mut config);
// Build stores
let stores = Stores::parse_all(&mut config, false).await;
// Parse core
let tracers = Telemetry::parse(&mut config, &stores);
let core = Core::parse(&mut config, stores, Default::default()).await;
let data = Data::parse(&mut config);
let cache = Caches::parse(&mut config);
let store = core.storage.data.clone();
let search_store = core.storage.fts.clone();
let (ipc, mut ipc_rxs) = build_ipc(false);
let inner = Arc::new(Inner {
shared_core: core.into_shared(),
data,
ipc,
cache,
});
// Parse acceptors
servers.parse_tcp_acceptors(&mut config, inner.clone());
// Enable tracing
tracers.enable(true);
// Start services
config.assert_no_errors();
ipc_rxs.spawn_queue_manager(inner.clone());
ipc_rxs.spawn_services(inner.clone());
// Spawn servers
let (shutdown_tx, _) = servers.spawn(|server, acceptor, shutdown_rx| {
match &server.protocol {
ServerProtocol::Smtp | ServerProtocol::Lmtp => server.spawn(
SmtpSessionManager::new(inner.clone()),
inner.clone(),
acceptor,
shutdown_rx,
),
ServerProtocol::Http => server.spawn(
HttpSessionManager::new(inner.clone()),
inner.clone(),
acceptor,
shutdown_rx,
),
ServerProtocol::Imap => server.spawn(
ImapSessionManager::new(inner.clone()),
inner.clone(),
acceptor,
shutdown_rx,
),
ServerProtocol::Pop3 => server.spawn(
Pop3SessionManager::new(inner.clone()),
inner.clone(),
acceptor,
shutdown_rx,
),
ServerProtocol::ManageSieve => server.spawn(
ManageSieveSessionManager::new(inner.clone()),
inner.clone(),
acceptor,
shutdown_rx,
),
};
});
if delete_if_exists {
store_destroy(&store).await;
search_store_destroy(&search_store).await;
}
// Create test accounts
let mut clients = AHashMap::new();
for (account, secret, name, email) in TEST_USERS {
let account_id = store
.create_test_user(account, secret, name, &[email])
.await;
clients.insert(
*account,
DummyWebDavClient::new(account_id, account, secret, email),
);
store
.add_permissions(
account,
[Permission::DavPrincipalList, Permission::DavPrincipalSearch],
)
.await;
if *account == "mike" {
store.set_test_quota(account, 1024).await;
}
}
store
.create_test_group("support", "Support Group", &["support@example.com"])
.await;
store.add_to_group("jane", "support").await;
WebDavTest {
server: inner.build_server(),
clients,
temp_dir,
shutdown_tx,
}
}
impl WebDavTest {
pub fn client(&self, name: &'static str) -> &DummyWebDavClient {
self.clients.get(name).unwrap()
}
pub async fn resources(&self, name: &'static str, collection: Collection) -> Arc<DavResources> {
let account_id = self.client(name).account_id;
let access_token = self.server.get_access_token(account_id).await.unwrap();
self.server
.fetch_dav_resources(&access_token, account_id, collection.into())
.await
.unwrap()
}
pub fn clear_cache(&self) {
for cache in [
&self.server.inner.cache.events,
&self.server.inner.cache.contacts,
&self.server.inner.cache.files,
] {
cache.clear();
}
}
pub async fn assert_is_empty(&self) {
assert_is_empty(&self.server).await;
self.clear_cache();
}
pub async fn wait_for_tasks(&self) {
wait_for_tasks(&self.server).await;
}
}
pub trait DavResourcesTest {
fn items(&self) -> Vec<DavResource>;
}
@@ -388,153 +322,3 @@ END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
"#;
impl WebDavTest {
pub async fn fetch_email(&self, account_id: u32, document_id: u32) -> Vec<u8> {
let metadata_ = self
.server
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::property(
account_id,
Collection::Email,
document_id,
EmailField::Metadata,
))
.await
.unwrap()
.unwrap();
self.server
.blob_store()
.get_blob(
metadata_
.unarchive::<MessageMetadata>()
.unwrap()
.blob_hash
.0
.as_slice(),
0..usize::MAX,
)
.await
.unwrap()
.unwrap()
}
}
const SERVER: &str = r#"
[server]
hostname = "webdav.example.org"
[spam-filter]
enable = false
[http]
url = "'https://127.0.0.1:8899'"
[server.listener.webdav]
bind = ["127.0.0.1:8899"]
protocol = "http"
max-connections = 81920
tls.implicit = true
[server.socket]
reuse-addr = true
[server.tls]
enable = true
implicit = false
certificate = "default"
[session.ehlo]
reject-non-fqdn = false
[session.rcpt]
relay = [ { if = "!is_empty(authenticated_as)", then = true },
{ else = false } ]
[session.rcpt.errors]
total = 5
wait = "1ms"
[resolver]
type = "system"
[queue.strategy]
route = [ { if = "rcpt_domain == 'example.com'", then = "'local'" },
{ else = "'mx'" } ]
[session.data.add-headers]
delivered-to = false
[session.extensions]
future-release = [ { if = "!is_empty(authenticated_as)", then = "99999999d"},
{ else = false } ]
[certificate.default]
cert = "%{file:{CERT}}%"
private-key = "%{file:{PK}}%"
[jmap.protocol]
set.max-objects = 100000
[jmap.protocol.request]
max-concurrent = 8
[jmap.protocol.upload]
max-size = 5000000
max-concurrent = 4
ttl = "1m"
[jmap.protocol.upload.quota]
files = 3
size = 50000
[jmap.rate-limit]
account = "1000/1m"
authentication = "100/2s"
anonymous = "100/1m"
[calendar.alarms]
minimum-interval = "1s"
[calendar.scheduling.inbound]
auto-add = true
[dav.collection]
assisted-discovery = {ASSISTED_DISCOVERY}
[sharing]
allow-directory-query = true
[store."auth"]
type = "sqlite"
path = "{TMP}/auth.db"
[store."auth".query]
name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true"
members = "SELECT member_of FROM group_members WHERE name = ?"
recipients = "SELECT name FROM emails WHERE address = ?"
emails = "SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC"
verify = "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type = 'primary' ORDER BY address LIMIT 5"
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || ? LIMIT 1"
[oauth]
key = "parerga_und_paralipomena"
[oauth.auth]
max-attempts = 1
[oauth.expiry]
user-code = "1s"
token = "1s"
refresh-token = "3s"
refresh-token-renew = "2s"
[tracer.console]
type = "console"
level = "{LEVEL}"
multiline = false
ansi = true
disabled-events = ["network.*"]
"#;

View File

@@ -4,14 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::webdav::{DummyWebDavClient, GenerateTestDavResource, prop::DavMultiStatus};
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
use dav_proto::schema::property::{CalDavProperty, CardDavProperty, DavProperty, WebDavProperty};
use groupware::DavResourceName;
use hyper::StatusCode;
pub async fn test(test: &WebDavTest) {
let client = test.client("john");
pub async fn test(test: &TestServer) {
let client = test.account("john@example.com").webdav_client();
for resource_type in [DavResourceName::Cal, DavResourceName::Card] {
println!(
@@ -22,7 +21,7 @@ pub async fn test(test: &WebDavTest) {
let mut paths = Vec::new();
for name in ["file1", "file2"] {
let contents = resource_type.generate();
let path = format!("{}/john/default/{}", resource_type.base_path(), name);
let path = format!("{}/john%40example.com/default/{}", resource_type.base_path(), name);
let etag = client
.request("PUT", &path, contents.as_str())
.await
@@ -33,7 +32,7 @@ pub async fn test(test: &WebDavTest) {
}
if resource_type == DavResourceName::Cal {
let path = format!("{}/john", resource_type.base_path());
let path = format!("{}/john%40example.com", resource_type.base_path());
let response = client
.multiget_calendar(&path, &[&paths[0].0, &paths[1].0])
.await;
@@ -49,7 +48,7 @@ pub async fn test(test: &WebDavTest) {
.with_values([contents.as_str()]);
}
} else {
let path = format!("{}/john", resource_type.base_path());
let path = format!("{}/john%40example.com", resource_type.base_path());
let response = client
.multiget_addressbook(&path, &[&paths[0].0, &paths[1].0])
.await;

View File

@@ -4,20 +4,29 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::WebDavTest;
use crate::{TEST_USERS, webdav::prop::ALL_DAV_PROPERTIES};
use crate::utils::server::TestServer;
use crate::webdav::prop::ALL_DAV_PROPERTIES;
use dav_proto::schema::property::{DavProperty, PrincipalProperty, WebDavProperty};
use groupware::DavResourceName;
use hyper::StatusCode;
pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
pub async fn test(test: &TestServer, assisted_discovery: bool) {
println!("Running principals tests...");
let client = test.client("jane");
let client = test.account("jane@example.com").webdav_client();
let principal_path = format!("D:href:{}/", DavResourceName::Principal.base_path());
let jane_principal_path = format!("D:href:{}/jane/", DavResourceName::Principal.base_path());
let jane_principal_path = format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Principal.base_path()
);
let path_support_card = format!("D:href:{}/support/", DavResourceName::Card.base_path());
let path_support_cal = format!("D:href:{}/support/", DavResourceName::Cal.base_path());
let path_support_card = format!(
"D:href:{}/support%40example.com/",
DavResourceName::Card.base_path()
);
let path_support_cal = format!(
"D:href:{}/support%40example.com/",
DavResourceName::Cal.base_path()
);
// Test 1: PROPFIND on /dav/pal should return all principals
let response = client
@@ -26,22 +35,34 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
ALL_DAV_PROPERTIES,
)
.await;
for (account, _, name, email) in TEST_USERS {
for account_ in test.accounts.values().filter(|a| a.name().contains('@')) {
let account_name = account_.name().replace('@', "%40");
let email = account_.name();
let description = account_.description();
let props = response.properties(&format!(
"{}/{}/",
DavResourceName::Principal.base_path(),
account
account_name
));
let path_pal = format!(
"D:href:{}/{}/",
DavResourceName::Principal.base_path(),
account
account_name
);
let path_card = format!(
"D:href:{}/{}/",
DavResourceName::Card.base_path(),
account_name
);
let path_cal = format!(
"D:href:{}/{}/",
DavResourceName::Cal.base_path(),
account_name
);
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_values([description])
.with_status(StatusCode::OK);
props
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
@@ -55,7 +76,7 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.get(DavProperty::WebDav(WebDavProperty::Owner))
.with_values([path_pal.as_str()])
.with_status(StatusCode::OK);
if *account == "jane" && !assisted_discovery {
if account_name == "jane%40example.com" && !assisted_discovery {
props
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
.with_values([path_cal.as_str(), path_support_cal.as_str()])
@@ -104,12 +125,16 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.with_status(StatusCode::OK);
props
.get(DavProperty::Principal(PrincipalProperty::CalendarUserType))
.with_values(["INDIVIDUAL"])
.with_values([if account_name == "support%40example.com" {
"GROUP"
} else {
"INDIVIDUAL"
}])
.with_status(StatusCode::OK);
props
.get(DavProperty::Principal(PrincipalProperty::ScheduleInboxURL))
.with_values([format!(
"D:href:{}/{account}/inbox/",
"D:href:{}/{account_name}/inbox/",
DavResourceName::Scheduling.base_path()
)
.as_str()])
@@ -117,7 +142,7 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
props
.get(DavProperty::Principal(PrincipalProperty::ScheduleOutboxURL))
.with_values([format!(
"D:href:{}/{account}/outbox/",
"D:href:{}/{account_name}/outbox/",
DavResourceName::Scheduling.base_path()
)
.as_str()])
@@ -210,24 +235,36 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
if assisted_discovery {
props
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
.with_values(
[format!("D:href:{}/jane/", DavResourceName::Cal.base_path()).as_str()],
.with_values([format!(
"D:href:{}/jane%40example.com/",
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_values([format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Card.base_path()
)
.as_str()])
.with_status(StatusCode::OK);
} else {
props
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
.with_values([
format!("D:href:{}/jane/", DavResourceName::Cal.base_path()).as_str(),
format!("D:href:{}/support/", DavResourceName::Cal.base_path()).as_str(),
format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Cal.base_path()
)
.as_str(),
format!(
"D:href:{}/support%40example.com/",
DavResourceName::Cal.base_path()
)
.as_str(),
])
.with_status(StatusCode::OK);
props
@@ -235,28 +272,49 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
PrincipalProperty::AddressbookHomeSet,
))
.with_values([
format!("D:href:{}/jane/", DavResourceName::Card.base_path()).as_str(),
format!("D:href:{}/support/", DavResourceName::Card.base_path()).as_str(),
format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Card.base_path()
)
.as_str(),
format!(
"D:href:{}/support%40example.com/",
DavResourceName::Card.base_path()
)
.as_str(),
])
.with_status(StatusCode::OK);
}
for (account, _, name, _) in TEST_USERS
.iter()
.filter(|(account, _, _, _)| ["jane", "support"].contains(account))
for account_ in test
.accounts
.values()
.filter(|account| ["jane@example.com", "support@example.com"].contains(&account.name()))
{
let path_card = format!("D:href:{}/{}/", DavResourceName::Card.base_path(), account);
let path_cal = format!("D:href:{}/{}/", DavResourceName::Cal.base_path(), account);
let account_name = account_.name().replace('@', "%40");
let description = account_.description();
let path_card = format!(
"D:href:{}/{}/",
DavResourceName::Card.base_path(),
account_name
);
let path_cal = format!(
"D:href:{}/{}/",
DavResourceName::Cal.base_path(),
account_name
);
let path_pal = format!(
"D:href:{}/{}/",
DavResourceName::Principal.base_path(),
account
account_name
);
let props = response.properties(&format!("{}/{account}/", resource_type.base_path()));
let props =
response.properties(&format!("{}/{account_name}/", resource_type.base_path()));
props
.get(DavProperty::WebDav(WebDavProperty::DisplayName))
.with_values([*name])
.with_values([description])
.with_status(StatusCode::OK);
props
.get(DavProperty::WebDav(WebDavProperty::ResourceType))
@@ -286,7 +344,7 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.get(DavProperty::WebDav(WebDavProperty::Owner))
.with_values([path_pal.as_str()])
.with_status(StatusCode::OK);
if *account == "jane" && !assisted_discovery {
if account_name == "jane%40example.com" && !assisted_discovery {
props
.get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet))
.with_values([path_cal.as_str(), path_support_cal.as_str()])
@@ -334,8 +392,8 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.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(),
format!("{}/jane%40example.com/", resource_type.base_path()).as_str(),
format!("{}/support%40example.com/", resource_type.base_path()).as_str(),
]);
}
@@ -350,8 +408,16 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.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(),
format!(
"{}/jane%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str(),
format!(
"{}/support%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str(),
]);
// Test 5: principal-search-property-set REPORT
@@ -384,17 +450,24 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.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(),
format!(
"{}/jane%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str(),
format!(
"{}/john%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str(),
]);
response
.properties(&format!("{}/jane/", DavResourceName::Principal.base_path()))
.properties(&format!(
"{}/jane%40example.com/",
DavResourceName::Principal.base_path()
))
.get(DavProperty::WebDav(WebDavProperty::DisplayName))
.with_values([TEST_USERS
.iter()
.find(|(account, _, _, _)| *account == "jane")
.unwrap()
.2])
.with_values([test.account("jane@example.com").description()])
.with_status(StatusCode::OK);
client
.request(
@@ -405,10 +478,16 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
.await
.with_status(StatusCode::MULTI_STATUS)
.into_propfind_response(None)
.with_hrefs([format!("{}/support/", DavResourceName::Principal.base_path()).as_str()]);
.with_hrefs([format!(
"{}/support%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str()]);
client.delete_default_containers().await;
client.delete_default_containers_by_account("support").await;
client
.delete_default_containers_by_account("support@example.com")
.await;
test.assert_is_empty().await;
}

View File

@@ -4,9 +4,9 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{DavResponse, DummyWebDavClient, WebDavTest};
use crate::webdav::{GenerateTestDavResource, TEST_ICAL_2, TEST_VTIMEZONE_1};
use ahash::{AHashMap, AHashSet};
use crate::utils::server::TestServer;
use crate::utils::webdav::GenerateTestDavResource;
use crate::webdav::{TEST_ICAL_2, TEST_VTIMEZONE_1};
use dav_proto::schema::property::{
CalDavProperty, CardDavProperty, DavProperty, PrincipalProperty, WebDavProperty,
};
@@ -14,8 +14,8 @@ use groupware::DavResourceName;
use hyper::StatusCode;
use types::dead_property::DeadElementTag;
pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
let client = test.client("jane");
pub async fn test(test: &TestServer, assisted_discovery: bool) {
let client = test.account("jane@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
@@ -26,8 +26,8 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
"Running PROPFIND/PROPPATCH tests ({})...",
resource_type.base_path()
);
let user_base_path = format!("{}/jane", resource_type.base_path());
let group_base_path = format!("{}/support", resource_type.base_path());
let user_base_path = format!("{}/jane%40example.com", resource_type.base_path());
let group_base_path = format!("{}/support%40example.com", resource_type.base_path());
// Create a new test container and file
let test_base_path = format!("{user_base_path}/PropFind_Folder/");
@@ -208,14 +208,18 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
]);
properties
.get(DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal))
.with_values([
format!("D:href:{}/jane/", DavResourceName::Principal.base_path()).as_str(),
]);
.with_values([format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str()]);
properties
.get(DavProperty::WebDav(WebDavProperty::Owner))
.with_values([
format!("D:href:{}/jane/", DavResourceName::Principal.base_path()).as_str(),
]);
.with_values([format!(
"D:href:{}/jane%40example.com/",
DavResourceName::Principal.base_path()
)
.as_str()]);
properties
.get(DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet))
.is_not_empty();
@@ -440,7 +444,7 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
] {
properties.get(prop).with_some_values([
format!(
"D:response.D:href:{}/jane/",
"D:response.D:href:{}/jane%40example.com/",
DavResourceName::Principal.base_path(),
)
.as_str(),
@@ -702,7 +706,9 @@ pub async fn test(test: &WebDavTest, assisted_discovery: bool) {
}
client.delete_default_containers().await;
client.delete_default_containers_by_account("support").await;
client
.delete_default_containers_by_account("support@example.com")
.await;
test.assert_is_empty().await;
}

View File

@@ -6,35 +6,45 @@
use types::collection::Collection;
use super::WebDavTest;
use crate::utils::server::TestServer;
use crate::utils::webdav::GenerateTestDavResource;
use crate::webdav::*;
pub async fn test(test: &WebDavTest) {
pub async fn test(test: &TestServer) {
println!("Running PUT/GET tests...");
let client = test.client("john");
let client = test.account("john@example.com").webdav_client();
// Simple PUT
let mut files = AHashMap::new();
for (path, ct, content) in [
("/dav/file/john/file1.txt", "text/plain", TEST_FILE_1),
("/dav/file/john/file2.txt", "text/x-other", TEST_FILE_2),
(
"/dav/card/john/default/card1.vcf",
"/dav/file/john%40example.com/file1.txt",
"text/plain",
TEST_FILE_1,
),
(
"/dav/file/john%40example.com/file2.txt",
"text/x-other",
TEST_FILE_2,
),
(
"/dav/card/john%40example.com/default/card1.vcf",
"text/vcard; charset=utf-8",
TEST_VCARD_1,
),
(
"/dav/card/john/default/card2.vcf",
"/dav/card/john%40example.com/default/card2.vcf",
"text/vcard; charset=utf-8",
TEST_VCARD_2,
),
(
"/dav/cal/john/default/event1.ics",
"/dav/cal/john%40example.com/default/event1.ics",
"text/calendar; charset=utf-8",
TEST_ICAL_1,
),
(
"/dav/cal/john/default/event2.ics",
"/dav/cal/john%40example.com/default/event2.ics",
"text/calendar; charset=utf-8",
TEST_ICAL_2,
),
@@ -62,9 +72,9 @@ pub async fn test(test: &WebDavTest) {
// PUT under a non-existing parent should fail
for (path, contents) in [
("/dav/file/john/foo/file1.txt", TEST_FILE_1),
("/dav/card/john/foo/card1.vcf", TEST_VCARD_1),
("/dav/cal/john/foo/event1.ics", TEST_ICAL_1),
("/dav/file/john%40example.com/foo/file1.txt", TEST_FILE_1),
("/dav/card/john%40example.com/foo/card1.vcf", TEST_VCARD_1),
("/dav/cal/john%40example.com/foo/event1.ics", TEST_ICAL_1),
] {
client
.request("PUT", path, contents)
@@ -74,13 +84,16 @@ pub async fn test(test: &WebDavTest) {
// PUT under resources should fail
for (path, contents) in [
("/dav/file/john/file1.txt/other-file.txt", TEST_FILE_1),
(
"/dav/card/john/default/card1.vcf/other-file.vcf",
"/dav/file/john%40example.com/file1.txt/other-file.txt",
TEST_FILE_1,
),
(
"/dav/card/john%40example.com/default/card1.vcf/other-file.vcf",
TEST_VCARD_1,
),
(
"/dav/cal/john/default/event1.ics/other-file.ical",
"/dav/cal/john%40example.com/default/event1.ics/other-file.ical",
TEST_ICAL_1,
),
] {
@@ -93,13 +106,13 @@ pub async fn test(test: &WebDavTest) {
// PUT a non-vCard/iCalendar file should fail
for (path, ct, content, precondition) in [
(
"/dav/card/john/card3.vcf",
"/dav/card/john%40example.com/card3.vcf",
"text/vcard; charset=utf-8",
TEST_FILE_1,
"B:supported-address-data",
),
(
"/dav/cal/john/event3.ics",
"/dav/cal/john%40example.com/event3.ics",
"text/calendar; charset=utf-8",
TEST_FILE_2,
"A:supported-calendar-data",
@@ -116,19 +129,19 @@ pub async fn test(test: &WebDavTest) {
let conf = &test.server.core.groupware;
for (path, contents, max_size, expect) in [
(
"/dav/file/john/chunky-file1.txt",
"/dav/file/john%40example.com/chunky-file1.txt",
TEST_FILE_1,
conf.max_file_size,
None,
),
(
"/dav/card/john/chunky-card1.vcf",
"/dav/card/john%40example.com/chunky-card1.vcf",
TEST_VCARD_1,
conf.max_vcard_size,
Some("B:max-resource-size"),
),
(
"/dav/cal/john/chunky-event1.ics",
"/dav/cal/john%40example.com/chunky-event1.ics",
TEST_ICAL_1,
conf.max_ical_size,
Some("A:max-resource-size"),
@@ -152,13 +165,16 @@ pub async fn test(test: &WebDavTest) {
}
// PUT requests cannot exceed quota
let mike_noquota = test.client("mike");
let mike_noquota = test.account("mike@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
DavResourceName::Card,
DavResourceName::Cal,
] {
let path = format!("{}/mike/quota-test/", resource_type.base_path());
let path = format!(
"{}/mike%40example.com/quota-test/",
resource_type.base_path()
);
mike_noquota
.mkcol("MKCOL", &path, [], [])
.await
@@ -199,25 +215,29 @@ pub async fn test(test: &WebDavTest) {
// PUT precondition enforcement
let modseq = [
test.resources("john", Collection::FileNode)
test.resources("john@example.com", Collection::FileNode)
.await
.highest_change_id,
test.resources("john", Collection::Calendar)
test.resources("john@example.com", Collection::Calendar)
.await
.highest_change_id,
test.resources("john", Collection::AddressBook)
test.resources("john@example.com", Collection::AddressBook)
.await
.highest_change_id,
];
for (path, ct, content) in [
("/dav/file/john/file1.txt", "text/plain", TEST_FILE_1),
(
"/dav/card/john/default/card1.vcf",
"/dav/file/john%40example.com/file1.txt",
"text/plain",
TEST_FILE_1,
),
(
"/dav/card/john%40example.com/default/card1.vcf",
"text/vcard; charset=utf-8",
TEST_VCARD_1,
),
(
"/dav/cal/john/default/event1.ics",
"/dav/cal/john%40example.com/default/event1.ics",
"text/calendar; charset=utf-8",
TEST_ICAL_1,
),
@@ -271,13 +291,13 @@ pub async fn test(test: &WebDavTest) {
}
assert_eq!(
[
test.resources("john", Collection::FileNode)
test.resources("john@example.com", Collection::FileNode)
.await
.highest_change_id,
test.resources("john", Collection::Calendar)
test.resources("john@example.com", Collection::Calendar)
.await
.highest_change_id,
test.resources("john", Collection::AddressBook)
test.resources("john@example.com", Collection::AddressBook)
.await
.highest_change_id,
],
@@ -315,18 +335,18 @@ pub async fn test(test: &WebDavTest) {
// PUT requests require unique UIDs
for (path, ct, content, precond_key, precond_value) in [
(
"/dav/card/john/default/card5.vcf",
"/dav/card/john%40example.com/default/card5.vcf",
"text/vcard; charset=utf-8",
TEST_VCARD_1,
"B:no-uid-conflict.D:href",
"/dav/card/john/default/card1.vcf",
"/dav/card/john%40example.com/default/card1.vcf",
),
(
"/dav/cal/john/default/event5.ics",
"/dav/cal/john%40example.com/default/event5.ics",
"text/calendar; charset=utf-8",
TEST_ICAL_1,
"A:no-uid-conflict.D:href",
"/dav/cal/john/default/event1.ics",
"/dav/cal/john%40example.com/default/event1.ics",
),
] {
client
@@ -345,7 +365,7 @@ pub async fn test(test: &WebDavTest) {
client
.request_with_headers(
"PUT",
"/dav/cal/john/default/invalid.ics",
"/dav/cal/john%40example.com/default/invalid.ics",
[
("content-type", "text/calendar; charset=utf-8"),
("if-none-match", "*"),
@@ -375,7 +395,7 @@ END:VCALENDAR
client
.request_with_headers(
"PUT",
"/dav/cal/john/default/invalid.ics",
"/dav/cal/john%40example.com/default/invalid.ics",
[
("content-type", "text/calendar; charset=utf-8"),
("if-none-match", "*"),
@@ -403,13 +423,25 @@ END:VCALENDAR
// Deleting unknown/invalid destinations should fail
for (path, expect) in [
("/dav/file/john/unknown.txt", StatusCode::NOT_FOUND),
("/dav/card/john/default/unknown.txt", StatusCode::NOT_FOUND),
("/dav/cal/john/default/unknown.txt", StatusCode::NOT_FOUND),
("/dav/file/john", StatusCode::FORBIDDEN),
("/dav/cal/john", StatusCode::FORBIDDEN),
("/dav/card/john", StatusCode::FORBIDDEN),
("/dav/pal/john", StatusCode::METHOD_NOT_ALLOWED),
(
"/dav/file/john%40example.com/unknown.txt",
StatusCode::NOT_FOUND,
),
(
"/dav/card/john%40example.com/default/unknown.txt",
StatusCode::NOT_FOUND,
),
(
"/dav/cal/john%40example.com/default/unknown.txt",
StatusCode::NOT_FOUND,
),
("/dav/file/john%40example.com", StatusCode::FORBIDDEN),
("/dav/cal/john%40example.com", StatusCode::FORBIDDEN),
("/dav/card/john%40example.com", StatusCode::FORBIDDEN),
(
"/dav/pal/john%40example.com",
StatusCode::METHOD_NOT_ALLOWED,
),
("/dav/file", StatusCode::FORBIDDEN),
("/dav/cal", StatusCode::FORBIDDEN),
("/dav/card", StatusCode::FORBIDDEN),

View File

@@ -4,15 +4,15 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{DavResponse, DummyWebDavClient, WebDavTest};
use crate::webdav::GenerateTestDavResource;
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
use ahash::AHashSet;
use dav_proto::Depth;
use groupware::DavResourceName;
use hyper::StatusCode;
pub async fn test(test: &WebDavTest) {
let client = test.client("john");
pub async fn test(test: &TestServer) {
let client = test.account("john@example.com").webdav_client();
for resource_type in [
DavResourceName::File,
@@ -23,7 +23,7 @@ pub async fn test(test: &WebDavTest) {
"Running REPORT sync-collection tests ({})...",
resource_type.base_path()
);
let user_base_path = format!("{}/john/", resource_type.base_path());
let user_base_path = format!("{}/john%40example.com/", resource_type.base_path());
// Test 1: Initial sync
let response = client