diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb2c8e4..faee39d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.16.0] - 2026-XX-XX + +This version includes **multiple breaking changes**. If you are upgrading from v0.15.x and below, please read the [upgrading documentation](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING/v0_16.md) for more information on how to upgrade from previous versions. + +## Added + +## Changed +- Directory: Removed `smtp`, `imap` and `memory` directory backends. + +## Fixed + ## [0.15.5] - 2026-02-14 If you are upgrading from v0.14.x and below, this version includes **multiple breaking changes**. Please read the [upgrading documentation](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING/v0_15.md) for more information on how to upgrade from previous versions. diff --git a/Cargo.lock b/Cargo.lock index 0308b7c1..c338fe6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,7 +1046,7 @@ dependencies = [ [[package]] name = "common" -version = "0.15.4" +version = "0.15.5" dependencies = [ "aes-gcm-siv", "ahash", @@ -1555,7 +1555,7 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "dav" -version = "0.15.4" +version = "0.15.5" dependencies = [ "calcard 0.3.2", "chrono", @@ -1578,7 +1578,7 @@ dependencies = [ [[package]] name = "dav-proto" -version = "0.15.4" +version = "0.15.5" dependencies = [ "calcard 0.3.2", "chrono", @@ -1744,7 +1744,7 @@ dependencies = [ [[package]] name = "directory" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "argon2", @@ -1976,7 +1976,7 @@ dependencies = [ [[package]] name = "email" -version = "0.15.4" +version = "0.15.5" dependencies = [ "aes", "aes-gcm", @@ -2091,7 +2091,7 @@ dependencies = [ [[package]] name = "event_macro" -version = "0.15.4" +version = "0.15.5" dependencies = [ "proc-macro2", "quote", @@ -2585,7 +2585,7 @@ dependencies = [ [[package]] name = "groupware" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "calcard 0.3.2", @@ -2893,7 +2893,7 @@ dependencies = [ [[package]] name = "http" -version = "0.15.4" +version = "0.15.5" dependencies = [ "async-stream", "base64 0.22.1", @@ -3004,7 +3004,7 @@ dependencies = [ [[package]] name = "http_proto" -version = "0.15.4" +version = "0.15.5" dependencies = [ "common", "compact_str", @@ -3305,7 +3305,7 @@ checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] name = "imap" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "common", @@ -3333,7 +3333,7 @@ dependencies = [ [[package]] name = "imap_proto" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "chrono", @@ -3555,7 +3555,7 @@ dependencies = [ [[package]] name = "jmap" -version = "0.15.4" +version = "0.15.5" dependencies = [ "aes-gcm", "aes-gcm-siv", @@ -3644,7 +3644,7 @@ dependencies = [ [[package]] name = "jmap_proto" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "calcard 0.3.2", @@ -4121,7 +4121,7 @@ dependencies = [ [[package]] name = "managesieve" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "common", @@ -4231,7 +4231,7 @@ checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492" [[package]] name = "migration" -version = "0.15.4" +version = "0.15.5" dependencies = [ "base64 0.22.1", "bincode 1.3.3", @@ -4473,7 +4473,7 @@ dependencies = [ [[package]] name = "nlp" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "bincode 1.3.3", @@ -5228,7 +5228,7 @@ dependencies = [ [[package]] name = "pop3" -version = "0.15.4" +version = "0.15.5" dependencies = [ "common", "directory", @@ -5414,7 +5414,7 @@ dependencies = [ [[package]] name = "proc_macros" -version = "0.15.4" +version = "0.15.5" dependencies = [ "proc-macro2", "quote", @@ -6945,7 +6945,7 @@ dependencies = [ [[package]] name = "services" -version = "0.15.4" +version = "0.15.5" dependencies = [ "aes-gcm", "aes-gcm-siv", @@ -7164,7 +7164,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smtp" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "blake3", @@ -7268,7 +7268,7 @@ dependencies = [ [[package]] name = "spam-filter" -version = "0.15.4" +version = "0.15.5" dependencies = [ "common", "compact_str", @@ -7367,7 +7367,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stalwart" -version = "0.15.4" +version = "0.15.5" dependencies = [ "common", "coordinator", @@ -7375,7 +7375,7 @@ dependencies = [ "directory", "email", "groupware", - "http 0.15.4", + "http 0.15.5", "imap", "jemallocator", "jmap", @@ -7400,7 +7400,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "store" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "arc-swap", @@ -7562,7 +7562,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "async-trait", @@ -7584,7 +7584,7 @@ dependencies = [ "form_urlencoded", "futures", "groupware", - "http 0.15.4", + "http 0.15.5", "http-body-util", "http_proto", "hyper 1.8.1", @@ -7606,6 +7606,7 @@ dependencies = [ "pop3", "quick-xml 0.38.4", "rayon", + "registry", "reqwest", "ring", "rkyv", @@ -8119,7 +8120,7 @@ dependencies = [ [[package]] name = "trc" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "base64 0.22.1", @@ -8207,7 +8208,7 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "types" -version = "0.15.4" +version = "0.15.5" dependencies = [ "blake3", "compact_str", @@ -8387,7 +8388,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utils" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ahash", "arcstr", diff --git a/crates/common/src/config/smtp/report.rs b/crates/common/src/config/smtp/report.rs index c533a3aa..461b31f3 100644 --- a/crates/common/src/config/smtp/report.rs +++ b/crates/common/src/config/smtp/report.rs @@ -106,8 +106,10 @@ impl ReportConfig { ObjectType::DkimReportSettings.singleton(), &dkim.ctx_from_address(), ), - subject: bp - .compile_expr(ObjectType::DkimReportSettings.singleton(), &dkim.ctx_subject()), + subject: bp.compile_expr( + ObjectType::DkimReportSettings.singleton(), + &dkim.ctx_subject(), + ), sign: bp.compile_expr( ObjectType::DkimReportSettings.singleton(), &dkim.ctx_dkim_sign_domain(), @@ -118,12 +120,18 @@ impl ReportConfig { ), }, spf: Report { - name: bp.compile_expr(ObjectType::SpfReportSettings.singleton(), &spf.ctx_from_name()), + name: bp.compile_expr( + ObjectType::SpfReportSettings.singleton(), + &spf.ctx_from_name(), + ), address: bp.compile_expr( ObjectType::SpfReportSettings.singleton(), &spf.ctx_from_address(), ), - subject: bp.compile_expr(ObjectType::SpfReportSettings.singleton(), &spf.ctx_subject()), + subject: bp.compile_expr( + ObjectType::SpfReportSettings.singleton(), + &spf.ctx_subject(), + ), sign: bp.compile_expr( ObjectType::SpfReportSettings.singleton(), &spf.ctx_dkim_sign_domain(), @@ -186,13 +194,18 @@ impl ReportConfig { ), }, tls: AggregateReport { - name: bp.compile_expr(ObjectType::TlsReportSettings.singleton(), &tls.ctx_from_name()), + name: bp.compile_expr( + ObjectType::TlsReportSettings.singleton(), + &tls.ctx_from_name(), + ), address: bp.compile_expr( ObjectType::TlsReportSettings.singleton(), &tls.ctx_from_address(), ), - org_name: bp - .compile_expr(ObjectType::TlsReportSettings.singleton(), &tls.ctx_org_name()), + org_name: bp.compile_expr( + ObjectType::TlsReportSettings.singleton(), + &tls.ctx_org_name(), + ), contact_info: bp.compile_expr( ObjectType::TlsReportSettings.singleton(), &tls.ctx_contact_info(), diff --git a/crates/common/src/i18n.rs b/crates/common/src/i18n.rs index ebcb7b25..d5278152 100644 --- a/crates/common/src/i18n.rs +++ b/crates/common/src/i18n.rs @@ -18,7 +18,9 @@ mod tests { #[test] fn calendar_templates_include_minutes() { - for lang in ["en", "es", "fr", "de", "it", "pt", "nl", "da", "ca", "el", "sv", "pl"] { + for lang in [ + "en", "es", "fr", "de", "it", "pt", "nl", "da", "ca", "el", "sv", "pl", + ] { let locale = locale(lang).expect("locale must exist"); assert!( locale.calendar_date_template.contains("%M"), diff --git a/crates/dav-proto/src/requests/acl.rs b/crates/dav-proto/src/requests/acl.rs index 76390c51..c47d384d 100644 --- a/crates/dav-proto/src/requests/acl.rs +++ b/crates/dav-proto/src/requests/acl.rs @@ -5,15 +5,15 @@ */ use crate::{ - parser::{tokenizer::Tokenizer, DavParser, Token}, + parser::{DavParser, Token, tokenizer::Tokenizer}, schema::{ + Element, NamedElement, Namespace, property::{DavValue, Privilege}, request::{ Acl, AclPrincipalPropSet, DavPropertyValue, PrincipalMatch, PrincipalMatchProperties, PrincipalPropertySearch, PropertySearch, }, response::{Ace, GrantDeny, Href, List, Principal}, - Element, NamedElement, Namespace, }, }; diff --git a/crates/dav-proto/src/requests/lockinfo.rs b/crates/dav-proto/src/requests/lockinfo.rs index 34259f08..47f6837d 100644 --- a/crates/dav-proto/src/requests/lockinfo.rs +++ b/crates/dav-proto/src/requests/lockinfo.rs @@ -4,16 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use types::dead_property::DeadProperty; - use crate::{ - parser::{tokenizer::Tokenizer, DavParser, Token}, + parser::{DavParser, Token, tokenizer::Tokenizer}, schema::{ + Element, NamedElement, Namespace, property::{LockScope, LockType}, request::LockInfo, - Element, NamedElement, Namespace, }, }; +use types::dead_property::DeadProperty; impl DavParser for LockInfo { fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result { diff --git a/crates/dav-proto/src/requests/mkcol.rs b/crates/dav-proto/src/requests/mkcol.rs index 7af6aba0..da919e87 100644 --- a/crates/dav-proto/src/requests/mkcol.rs +++ b/crates/dav-proto/src/requests/mkcol.rs @@ -5,8 +5,8 @@ */ use crate::{ - parser::{tokenizer::Tokenizer, DavParser, Token}, - schema::{request::MkCol, Element, NamedElement, Namespace}, + parser::{DavParser, Token, tokenizer::Tokenizer}, + schema::{Element, NamedElement, Namespace, request::MkCol}, }; impl DavParser for MkCol { diff --git a/crates/dav-proto/src/requests/mod.rs b/crates/dav-proto/src/requests/mod.rs index c15af3e8..3b233a42 100644 --- a/crates/dav-proto/src/requests/mod.rs +++ b/crates/dav-proto/src/requests/mod.rs @@ -5,7 +5,7 @@ */ use crate::{ - parser::{tokenizer::Tokenizer, DavParser, RawElement, Token}, + parser::{DavParser, RawElement, Token, tokenizer::Tokenizer}, schema::Namespace, }; use types::dead_property::{DeadElementTag, DeadProperty, DeadPropertyTag}; @@ -112,7 +112,7 @@ impl From<&RawElement<'_>> for DeadElementTag { #[cfg(test)] mod tests { use crate::{ - parser::{tokenizer::Tokenizer, DavParser}, + parser::{DavParser, tokenizer::Tokenizer}, schema::request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report}, }; diff --git a/crates/dav-proto/src/requests/propertyupdate.rs b/crates/dav-proto/src/requests/propertyupdate.rs index 7d2fcb51..324b6fe3 100644 --- a/crates/dav-proto/src/requests/propertyupdate.rs +++ b/crates/dav-proto/src/requests/propertyupdate.rs @@ -5,8 +5,8 @@ */ use crate::{ - parser::{tokenizer::Tokenizer, DavParser, Token}, - schema::{request::PropertyUpdate, Element, NamedElement, Namespace}, + parser::{DavParser, Token, tokenizer::Tokenizer}, + schema::{Element, NamedElement, Namespace, request::PropertyUpdate}, }; impl DavParser for PropertyUpdate { diff --git a/crates/dav-proto/src/requests/propfind.rs b/crates/dav-proto/src/requests/propfind.rs index 76577751..7629830d 100644 --- a/crates/dav-proto/src/requests/propfind.rs +++ b/crates/dav-proto/src/requests/propfind.rs @@ -5,8 +5,8 @@ */ use crate::{ - parser::{tokenizer::Tokenizer, DavParser, Token}, - schema::{request::PropFind, Element, NamedElement, Namespace}, + parser::{DavParser, Token, tokenizer::Tokenizer}, + schema::{Element, NamedElement, Namespace, request::PropFind}, }; impl DavParser for PropFind { diff --git a/crates/dav-proto/src/requests/report.rs b/crates/dav-proto/src/requests/report.rs index 033c6231..fce3c2aa 100644 --- a/crates/dav-proto/src/requests/report.rs +++ b/crates/dav-proto/src/requests/report.rs @@ -5,11 +5,13 @@ */ use crate::{ + Depth, parser::{ - property::TimeRangeFromRaw, tokenizer::Tokenizer, DavParser, RawElement, Token, - XmlValueParser, + DavParser, RawElement, Token, XmlValueParser, property::TimeRangeFromRaw, + tokenizer::Tokenizer, }, schema::{ + Attribute, Collation, Element, MatchType, NamedElement, Namespace, property::DavProperty, request::{ AclPrincipalPropSet, AddressbookQuery, CalendarQuery, ExpandProperty, @@ -17,15 +19,13 @@ use crate::{ PrincipalPropertySearch, PropFind, Report, SyncCollection, TextMatch, Timezone, VCardPropertyWithGroup, }, - Attribute, Collation, Element, MatchType, NamedElement, Namespace, }, - Depth, }; use calcard::{ icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty}, vcard::VCardParameterName, }; -use types::{dead_property::DeadElementTag, TimeRange}; +use types::{TimeRange, dead_property::DeadElementTag}; impl DavParser for Report { fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result { diff --git a/crates/dav-proto/src/responses/acl.rs b/crates/dav-proto/src/responses/acl.rs index 7d3157ed..829c5fc7 100644 --- a/crates/dav-proto/src/responses/acl.rs +++ b/crates/dav-proto/src/responses/acl.rs @@ -4,19 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - use crate::{ responses::XmlEscape, schema::{ + Namespace, Namespaces, property::{DavProperty, Privilege}, response::{ Ace, AclRestrictions, GrantDeny, Href, List, Principal, PrincipalSearchProperty, PrincipalSearchPropertySet, RequiredPrincipal, Resource, SupportedPrivilege, }, - Namespace, Namespaces, }, }; +use std::fmt::Display; impl Display for SupportedPrivilege { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/dav-proto/src/responses/error.rs b/crates/dav-proto/src/responses/error.rs index dbab9c5b..14e5b0bd 100644 --- a/crates/dav-proto/src/responses/error.rs +++ b/crates/dav-proto/src/responses/error.rs @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - use crate::schema::{ - response::{BaseCondition, CalCondition, CardCondition, Condition, ErrorResponse}, Namespace, Namespaces, + response::{BaseCondition, CalCondition, CardCondition, Condition, ErrorResponse}, }; +use std::fmt::Display; impl Display for ErrorResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/dav-proto/src/responses/lock.rs b/crates/dav-proto/src/responses/lock.rs index 4ef521d8..a7d1aaac 100644 --- a/crates/dav-proto/src/responses/lock.rs +++ b/crates/dav-proto/src/responses/lock.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use types::dead_property::DeadProperty; - use crate::{ + Depth, Timeout, responses::DeadPropertyFormat, schema::{ property::{ActiveLock, LockDiscovery, LockEntry, LockScope, LockType, SupportedLock}, request::LockInfo, response::{Href, List}, }, - Depth, Timeout, }; +use std::fmt::Display; +use types::dead_property::DeadProperty; impl Display for SupportedLock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/dav-proto/src/responses/mkcol.rs b/crates/dav-proto/src/responses/mkcol.rs index 48102ede..e333cc2a 100644 --- a/crates/dav-proto/src/responses/mkcol.rs +++ b/crates/dav-proto/src/responses/mkcol.rs @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - use crate::schema::{ - response::{List, MkColResponse, PropStat}, Namespace, Namespaces, + response::{List, MkColResponse, PropStat}, }; +use std::fmt::Display; impl Display for MkColResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/dav-proto/src/responses/mod.rs b/crates/dav-proto/src/responses/mod.rs index f4c64d1d..0d9ea5d2 100644 --- a/crates/dav-proto/src/responses/mod.rs +++ b/crates/dav-proto/src/responses/mod.rs @@ -13,14 +13,13 @@ pub mod property; pub mod propstat; pub mod schedule; -use types::dead_property::{DeadProperty, DeadPropertyTag}; - use crate::schema::{ + Namespaces, property::{Comp, ResourceType, SupportedCollation}, response::{Href, List, Location, ResponseDescription, Status, SyncToken}, - Namespaces, }; use std::fmt::{Display, Write}; +use types::dead_property::{DeadProperty, DeadPropertyTag}; trait XmlEscape { fn write_escaped_to(&self, out: &mut impl Write) -> std::fmt::Result; @@ -211,9 +210,11 @@ mod tests { use types::dead_property::{DeadElementTag, DeadProperty, DeadPropertyTag}; use crate::{ - parser::{tokenizer::Tokenizer, Token}, + Depth, + parser::{Token, tokenizer::Tokenizer}, responses::XmlCdataEscape, schema::{ + Namespace, property::{ ActiveLock, CalDavProperty, CardDavProperty, DavValue, LockScope, Privilege, ResourceType, Rfc1123DateTime, SupportedLock, WebDavProperty, @@ -225,9 +226,7 @@ mod tests { PrincipalSearchPropertySet, PropResponse, PropStat, RequiredPrincipal, Resource, Response, ScheduleResponse, ScheduleResponseItem, SupportedPrivilege, }, - Namespace, }, - Depth, }; impl List { @@ -325,29 +324,33 @@ mod tests { ]) .to_string(), // 004.xml - MultiStatus::new(vec![Response::new_status( - ["http://www.example.com/container/resource3"], - StatusCode::LOCKED, - ) - .with_error(BaseCondition::LockTokenSubmitted(List(vec![])))]) + MultiStatus::new(vec![ + Response::new_status( + ["http://www.example.com/container/resource3"], + StatusCode::LOCKED, + ) + .with_error(BaseCondition::LockTokenSubmitted(List(vec![]))), + ]) .to_string(), // 005.xml PropResponse::new(vec![DavPropertyValue::new( WebDavProperty::LockDiscovery, - vec![ActiveLock::new( - "http://example.com/workspace/webdav/proposal.doc", - LockScope::Exclusive, - ) - .with_owner(DeadProperty(vec![ - DeadPropertyTag::ElementStart(DeadElementTag { - name: "D:href".to_string(), - attrs: None, - }), - DeadPropertyTag::Text("http://example.org/~ejw/contact.html".to_string()), - DeadPropertyTag::ElementEnd, - ])) - .with_timeout(604800) - .with_lock_token("urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4")], + vec![ + ActiveLock::new( + "http://example.com/workspace/webdav/proposal.doc", + LockScope::Exclusive, + ) + .with_owner(DeadProperty(vec![ + DeadPropertyTag::ElementStart(DeadElementTag { + name: "D:href".to_string(), + attrs: None, + }), + DeadPropertyTag::Text("http://example.org/~ejw/contact.html".to_string()), + DeadPropertyTag::ElementEnd, + ])) + .with_timeout(604800) + .with_lock_token("urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4"), + ], )]) .to_string(), // 006.xml @@ -495,41 +498,43 @@ END:VCARD "http://www.example.com/papers/", vec![PropStat::new_list(vec![DavPropertyValue::new( WebDavProperty::SupportedPrivilegeSet, - vec![SupportedPrivilege::new(Privilege::All, "Any operation") - .with_abstract() - .with_supported_privilege( - SupportedPrivilege::new(Privilege::Read, "Read any object") - .with_supported_privilege( - SupportedPrivilege::new(Privilege::ReadAcl, "Read ACL") - .with_abstract(), - ) - .with_supported_privilege( - SupportedPrivilege::new( - Privilege::ReadCurrentUserPrivilegeSet, - "Read current user privilege set property", + vec![ + SupportedPrivilege::new(Privilege::All, "Any operation") + .with_abstract() + .with_supported_privilege( + SupportedPrivilege::new(Privilege::Read, "Read any object") + .with_supported_privilege( + SupportedPrivilege::new(Privilege::ReadAcl, "Read ACL") + .with_abstract(), ) - .with_abstract(), - ), - ) - .with_supported_privilege( - SupportedPrivilege::new(Privilege::Write, "Write any object") - .with_supported_privilege( - SupportedPrivilege::new(Privilege::WriteAcl, "Write ACL") + .with_supported_privilege( + SupportedPrivilege::new( + Privilege::ReadCurrentUserPrivilegeSet, + "Read current user privilege set property", + ) .with_abstract(), - ) - .with_supported_privilege(SupportedPrivilege::new( - Privilege::WriteProperties, - "Write properties", - )) - .with_supported_privilege(SupportedPrivilege::new( - Privilege::WriteContent, - "Write resource content", - )), - ) - .with_supported_privilege(SupportedPrivilege::new( - Privilege::Unlock, - "Unlock resource", - ))], + ), + ) + .with_supported_privilege( + SupportedPrivilege::new(Privilege::Write, "Write any object") + .with_supported_privilege( + SupportedPrivilege::new(Privilege::WriteAcl, "Write ACL") + .with_abstract(), + ) + .with_supported_privilege(SupportedPrivilege::new( + Privilege::WriteProperties, + "Write properties", + )) + .with_supported_privilege(SupportedPrivilege::new( + Privilege::WriteContent, + "Write resource content", + )), + ) + .with_supported_privilege(SupportedPrivilege::new( + Privilege::Unlock, + "Unlock resource", + )), + ], )])], )]) .to_string(), @@ -592,24 +597,26 @@ END:VCARD ), DavPropertyValue::new( WebDavProperty::SupportedPrivilegeSet, - vec![SupportedPrivilege::new(Privilege::All, "Any operation") - .with_abstract() - .with_supported_privilege(SupportedPrivilege::new( - Privilege::Read, - "Read any object", - )) - .with_supported_privilege( - SupportedPrivilege::new(Privilege::Write, "Write any object") - .with_abstract(), - ) - .with_supported_privilege(SupportedPrivilege::new( - Privilege::ReadAcl, - "Read the ACL", - )) - .with_supported_privilege(SupportedPrivilege::new( - Privilege::WriteAcl, - "Write the ACL", - ))], + vec![ + SupportedPrivilege::new(Privilege::All, "Any operation") + .with_abstract() + .with_supported_privilege(SupportedPrivilege::new( + Privilege::Read, + "Read any object", + )) + .with_supported_privilege( + SupportedPrivilege::new(Privilege::Write, "Write any object") + .with_abstract(), + ) + .with_supported_privilege(SupportedPrivilege::new( + Privilege::ReadAcl, + "Read the ACL", + )) + .with_supported_privilege(SupportedPrivilege::new( + Privilege::WriteAcl, + "Write the ACL", + )), + ], ), DavPropertyValue::new( WebDavProperty::CurrentUserPrivilegeSet, diff --git a/crates/dav-proto/src/responses/multistatus.rs b/crates/dav-proto/src/responses/multistatus.rs index ccb9efd4..0d10e73e 100644 --- a/crates/dav-proto/src/responses/multistatus.rs +++ b/crates/dav-proto/src/responses/multistatus.rs @@ -4,17 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use hyper::StatusCode; - use crate::schema::{ + Namespace, Namespaces, response::{ Condition, Href, List, Location, MultiStatus, PropStat, Response, ResponseDescription, ResponseType, Status, SyncToken, }, - Namespace, Namespaces, }; +use hyper::StatusCode; +use std::fmt::Display; impl Display for MultiStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/dav-proto/src/responses/property.rs b/crates/dav-proto/src/responses/property.rs index 7f03ad2a..9be7e92d 100644 --- a/crates/dav-proto/src/responses/property.rs +++ b/crates/dav-proto/src/responses/property.rs @@ -8,6 +8,7 @@ use super::{XmlCdataEscape, XmlEscape}; use crate::{ responses::DeadPropertyFormat, schema::{ + Namespace, Namespaces, property::{ ActiveLock, CalDavProperty, CardDavProperty, Comp, DavProperty, DavValue, LockDiscovery, LockEntry, PrincipalProperty, Privilege, ReportSet, ResourceType, @@ -15,13 +16,12 @@ use crate::{ }, request::DavPropertyValue, response::{Ace, AclRestrictions, Href, List, PropResponse, SupportedPrivilege}, - Namespace, Namespaces, }, }; use calcard::icalendar::ICalendarComponentType; use mail_parser::{ - parsers::fields::date::{DOW, MONTH}, DateTime, + parsers::fields::date::{DOW, MONTH}, }; use std::fmt::Display; use types::dead_property::DeadProperty; @@ -217,7 +217,7 @@ impl DavProperty { PrincipalProperty::ScheduleOutboxURL => "A:schedule-outbox-URL", }, DavProperty::DeadProperty(dead) => { - return (dead.name.as_str(), dead.attrs.as_deref()) + return (dead.name.as_str(), dead.attrs.as_deref()); } }, None, diff --git a/crates/dav-proto/src/responses/propstat.rs b/crates/dav-proto/src/responses/propstat.rs index 02673aa3..4d1089dd 100644 --- a/crates/dav-proto/src/responses/propstat.rs +++ b/crates/dav-proto/src/responses/propstat.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use hyper::StatusCode; - use crate::schema::{ request::DavPropertyValue, response::{Condition, List, Prop, PropStat, ResponseDescription, Status}, }; +use hyper::StatusCode; +use std::fmt::Display; impl Display for PropStat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -55,7 +53,7 @@ impl PropStat { } pub fn with_prop(mut self, prop: impl Into) -> Self { - self.prop.0 .0.push(prop.into()); + self.prop.0.0.push(prop.into()); self } diff --git a/crates/dav-proto/src/responses/schedule.rs b/crates/dav-proto/src/responses/schedule.rs index ac504b58..911c2a40 100644 --- a/crates/dav-proto/src/responses/schedule.rs +++ b/crates/dav-proto/src/responses/schedule.rs @@ -7,8 +7,8 @@ use crate::{ responses::{XmlCdataEscape, XmlEscape}, schema::{ - response::{ScheduleResponse, ScheduleResponseItem}, Namespaces, + response::{ScheduleResponse, ScheduleResponseItem}, }, }; use std::fmt::Display; diff --git a/crates/dav-proto/src/schema/property.rs b/crates/dav-proto/src/schema/property.rs index 459ebe94..38d69238 100644 --- a/crates/dav-proto/src/schema/property.rs +++ b/crates/dav-proto/src/schema/property.rs @@ -5,9 +5,9 @@ */ use super::{ + Collation, Namespace, request::DavPropertyValue, response::{Ace, AclRestrictions, Href, List, Response, SupportedPrivilege}, - Collation, Namespace, }; use crate::{Depth, Timeout}; use calcard::{ @@ -15,8 +15,8 @@ use calcard::{ vcard::{VCard, VCardProperty}, }; use types::{ - dead_property::{DeadElementTag, DeadProperty}, TimeRange, + dead_property::{DeadElementTag, DeadProperty}, }; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/dav-proto/src/schema/response.rs b/crates/dav-proto/src/schema/response.rs index d4f8cb49..cd507a28 100644 --- a/crates/dav-proto/src/schema/response.rs +++ b/crates/dav-proto/src/schema/response.rs @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{borrow::Cow, fmt::Display}; - +use super::{ + Namespaces, + property::{DavProperty, Privilege}, + request::{DavPropertyValue, Filter}, +}; use calcard::{ icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty}, vcard::{VCardParameterName, VCardProperty}, }; use hyper::StatusCode; - -use super::{ - property::{DavProperty, Privilege}, - request::{DavPropertyValue, Filter}, - Namespaces, -}; +use std::{borrow::Cow, fmt::Display}; pub struct MultiStatus { pub namespaces: Namespaces, diff --git a/crates/dav/src/calendar/freebusy.rs b/crates/dav/src/calendar/freebusy.rs index 56e6592e..5ffa742a 100644 --- a/crates/dav/src/calendar/freebusy.rs +++ b/crates/dav/src/calendar/freebusy.rs @@ -69,7 +69,11 @@ impl CalendarFreebusyRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/calendar/get.rs b/crates/dav/src/calendar/get.rs index b61cba5c..f786aeaf 100644 --- a/crates/dav/src/calendar/get.rs +++ b/crates/dav/src/calendar/get.rs @@ -50,7 +50,11 @@ impl CalendarGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/card/get.rs b/crates/dav/src/card/get.rs index 1a68dfd1..de1a5c09 100644 --- a/crates/dav/src/card/get.rs +++ b/crates/dav/src/card/get.rs @@ -50,7 +50,11 @@ impl CardGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/card/query.rs b/crates/dav/src/card/query.rs index 6d8030e0..7025469d 100644 --- a/crates/dav/src/card/query.rs +++ b/crates/dav/src/card/query.rs @@ -55,7 +55,11 @@ impl CardQueryRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; let Some(resource) = resources.by_path( diff --git a/crates/dav/src/file/get.rs b/crates/dav/src/file/get.rs index 0a04655d..4ef5a71b 100644 --- a/crates/dav/src/file/get.rs +++ b/crates/dav/src/file/get.rs @@ -51,7 +51,11 @@ impl FileGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let files = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; let resource = files.map_resource(&resource_)?; diff --git a/crates/dav/src/file/mkcol.rs b/crates/dav/src/file/mkcol.rs index 85d981a1..e9d9249c 100644 --- a/crates/dav/src/file/mkcol.rs +++ b/crates/dav/src/file/mkcol.rs @@ -53,7 +53,11 @@ impl FileMkColRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; let resource = resources.map_parent_resource(&resource_)?; diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs index 3f388ad2..553b70eb 100644 --- a/crates/directory/src/lib.rs +++ b/crates/directory/src/lib.rs @@ -15,6 +15,7 @@ use std::{collections::HashMap, fmt::Debug, sync::Arc}; pub mod backend; pub mod core; +#[derive(Debug, PartialEq, Eq)] pub enum Credentials { Basic { username: String, diff --git a/crates/imap/src/op/enable.rs b/crates/imap/src/op/enable.rs index 435868f7..a6cd8966 100644 --- a/crates/imap/src/op/enable.rs +++ b/crates/imap/src/op/enable.rs @@ -4,16 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Instant; - use crate::core::Session; use common::network::SessionStream; -use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ImapResponse, ProtocolVersion, capability::Capability, enable}, receiver::Request, }; +use registry::schema::enums::Permission; +use std::time::Instant; impl Session { pub async fn handle_enable(&mut self, request: Request) -> trc::Result<()> { diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index bbae6631..4f2b371f 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -11,7 +11,6 @@ use crate::{ }; use ahash::AHashMap; use common::{network::SessionStream, storage::index::ObjectIndexBuilder}; -use registry::schema::enums::Permission; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, message::metadata::{ @@ -33,6 +32,7 @@ use imap_proto::{ }, receiver::Request, }; +use registry::schema::enums::Permission; use std::{borrow::Cow, sync::Arc, time::Instant}; use store::{ ValueKey, diff --git a/crates/imap/src/op/idle.rs b/crates/imap/src/op/idle.rs index e1eaef7c..737d5dfc 100644 --- a/crates/imap/src/op/idle.rs +++ b/crates/imap/src/op/idle.rs @@ -10,7 +10,6 @@ use crate::{ }; use ahash::AHashSet; use common::{ipc::PushNotification, network::SessionStream}; -use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ @@ -20,6 +19,7 @@ use imap_proto::{ }, receiver::Request, }; +use registry::schema::enums::Permission; use std::{sync::Arc, time::Instant}; use store::query::log::Query; use tokio::io::AsyncReadExt; diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 0d487c89..2d02e1ff 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -7,7 +7,6 @@ use super::{ImapContext, ToModSeq}; use crate::core::{SavedSearch, SelectedMailbox, Session, State}; use common::network::SessionStream; -use registry::schema::enums::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::{ @@ -17,6 +16,7 @@ use imap_proto::{ }, receiver::Request, }; +use registry::schema::enums::Permission; use std::{sync::Arc, time::Instant}; use types::id::Id; diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index 01d70148..f1c9b096 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -11,7 +11,6 @@ use crate::{ }; use ahash::AHashSet; use common::{network::SessionStream, storage::index::ObjectIndexBuilder}; -use registry::schema::enums::Permission; use email::{ mailbox::TRASH_ID, message::{ingest::EmailIngest, metadata::MessageData}, @@ -25,6 +24,7 @@ use imap_proto::{ }, receiver::Request, }; +use registry::schema::enums::Permission; use std::{sync::Arc, time::Instant}; use store::{ ValueKey, diff --git a/crates/imap/src/op/subscribe.rs b/crates/imap/src/op/subscribe.rs index eadd11b8..22e1df61 100644 --- a/crates/imap/src/op/subscribe.rs +++ b/crates/imap/src/op/subscribe.rs @@ -10,8 +10,8 @@ use crate::{ spawn_op, }; use common::{network::SessionStream, storage::index::ObjectIndexBuilder}; -use registry::schema::enums::Permission; use imap_proto::{Command, ResponseCode, StatusResponse, receiver::Request}; +use registry::schema::enums::Permission; use std::time::Instant; use store::{ ValueKey, diff --git a/crates/imap/src/op/thread.rs b/crates/imap/src/op/thread.rs index 55ec0ffa..94dc836d 100644 --- a/crates/imap/src/op/thread.rs +++ b/crates/imap/src/op/thread.rs @@ -10,7 +10,6 @@ use crate::{ }; use ahash::AHashMap; use common::network::SessionStream; -use registry::schema::enums::Permission; use email::cache::{MessageCacheFetch, email::MessageCacheAccess}; use imap_proto::{ Command, StatusResponse, @@ -20,6 +19,7 @@ use imap_proto::{ }, receiver::Request, }; +use registry::schema::enums::Permission; use std::{sync::Arc, time::Instant}; use trc::AddContext; diff --git a/crates/jmap-proto/src/object/search_snippet.rs b/crates/jmap-proto/src/object/search_snippet.rs index b3491b30..0da60e7f 100644 --- a/crates/jmap-proto/src/object/search_snippet.rs +++ b/crates/jmap-proto/src/object/search_snippet.rs @@ -11,7 +11,6 @@ use types::id::Id; #[derive(Debug, Clone, Default)] pub struct SearchSnippet; - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SearchSnippetProperty { EmailId, diff --git a/crates/jmap-proto/src/request/method.rs b/crates/jmap-proto/src/request/method.rs index 32ea8f22..52ada7e7 100644 --- a/crates/jmap-proto/src/request/method.rs +++ b/crates/jmap-proto/src/request/method.rs @@ -4,8 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use registry::{ + schema::prelude::{OBJ_SINGLETON, ObjectType}, + types::EnumImpl, +}; use std::{borrow::Cow, fmt::Display}; -use registry::{schema::prelude::{OBJ_SINGLETON, ObjectType}, types::EnumImpl}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MethodName { @@ -124,7 +127,9 @@ impl MethodName { (MethodFunction::Query, MethodObject::Principal) => "Principal/query", (MethodFunction::Changes, MethodObject::Principal) => "Principal/changes", (MethodFunction::QueryChanges, MethodObject::Principal) => "Principal/queryChanges", - (MethodFunction::GetAvailability, MethodObject::Principal) => "Principal/getAvailability", + (MethodFunction::GetAvailability, MethodObject::Principal) => { + "Principal/getAvailability" + } (MethodFunction::Get, MethodObject::Quota) => "Quota/get", (MethodFunction::Changes, MethodObject::Quota) => "Quota/changes", @@ -155,9 +160,13 @@ impl MethodName { (MethodFunction::Set, MethodObject::FileNode) => "FileNode/set", (MethodFunction::Get, MethodObject::ShareNotification) => "ShareNotification/get", - (MethodFunction::Changes, MethodObject::ShareNotification) => "ShareNotification/changes", + (MethodFunction::Changes, MethodObject::ShareNotification) => { + "ShareNotification/changes" + } (MethodFunction::Query, MethodObject::ShareNotification) => "ShareNotification/query", - (MethodFunction::QueryChanges, MethodObject::ShareNotification) => "ShareNotification/queryChanges", + (MethodFunction::QueryChanges, MethodObject::ShareNotification) => { + "ShareNotification/queryChanges" + } (MethodFunction::Set, MethodObject::ShareNotification) => "ShareNotification/set", (MethodFunction::Get, MethodObject::Calendar) => "Calendar/get", @@ -167,19 +176,33 @@ impl MethodName { (MethodFunction::Get, MethodObject::CalendarEvent) => "CalendarEvent/get", (MethodFunction::Changes, MethodObject::CalendarEvent) => "CalendarEvent/changes", (MethodFunction::Query, MethodObject::CalendarEvent) => "CalendarEvent/query", - (MethodFunction::QueryChanges, MethodObject::CalendarEvent) => "CalendarEvent/queryChanges", + (MethodFunction::QueryChanges, MethodObject::CalendarEvent) => { + "CalendarEvent/queryChanges" + } (MethodFunction::Set, MethodObject::CalendarEvent) => "CalendarEvent/set", (MethodFunction::Copy, MethodObject::CalendarEvent) => "CalendarEvent/copy", (MethodFunction::Parse, MethodObject::CalendarEvent) => "CalendarEvent/parse", - (MethodFunction::Get, MethodObject::CalendarEventNotification) => "CalendarEventNotification/get", - (MethodFunction::Changes, MethodObject::CalendarEventNotification) => "CalendarEventNotification/changes", - (MethodFunction::Query, MethodObject::CalendarEventNotification) => "CalendarEventNotification/query", - (MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => "CalendarEventNotification/queryChanges", - (MethodFunction::Set, MethodObject::CalendarEventNotification) => "CalendarEventNotification/set", + (MethodFunction::Get, MethodObject::CalendarEventNotification) => { + "CalendarEventNotification/get" + } + (MethodFunction::Changes, MethodObject::CalendarEventNotification) => { + "CalendarEventNotification/changes" + } + (MethodFunction::Query, MethodObject::CalendarEventNotification) => { + "CalendarEventNotification/query" + } + (MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => { + "CalendarEventNotification/queryChanges" + } + (MethodFunction::Set, MethodObject::CalendarEventNotification) => { + "CalendarEventNotification/set" + } (MethodFunction::Get, MethodObject::ParticipantIdentity) => "ParticipantIdentity/get", - (MethodFunction::Changes, MethodObject::ParticipantIdentity) => "ParticipantIdentity/changes", + (MethodFunction::Changes, MethodObject::ParticipantIdentity) => { + "ParticipantIdentity/changes" + } (MethodFunction::Set, MethodObject::ParticipantIdentity) => "ParticipantIdentity/set", (MethodFunction::Echo, MethodObject::Core) => "Core/echo", @@ -187,11 +210,12 @@ impl MethodName { return Cow::Owned(format!("x:{}/{}", obj.as_str(), method.as_str())); } _ => "error", - }.into() + } + .into() } pub fn parse(s: &str) -> Option { - hashify::tiny_map!(s.as_bytes(), + hashify::tiny_map!(s.as_bytes(), "PushSubscription/get" => (MethodObject::PushSubscription, MethodFunction::Get), "PushSubscription/set" => (MethodObject::PushSubscription, MethodFunction::Set), @@ -314,7 +338,6 @@ impl MethodName { } }).map(|(obj, fnc)| MethodName { obj, fnc }) } - } impl Display for MethodObject { @@ -344,7 +367,7 @@ impl Display for MethodObject { MethodObject::Registry(obj) => { f.write_str("x:")?; return f.write_str(obj.as_str()); - }, + } }) } } @@ -374,7 +397,7 @@ impl MethodObject { match self { MethodObject::Registry(obj) => obj, _ => panic!("Not a registry method object"), - } + } } } @@ -385,9 +408,8 @@ impl<'de> serde::Deserialize<'de> for MethodName { { let value = <&str>::deserialize(deserializer)?; - MethodName::parse(value).ok_or_else(|| { - serde::de::Error::custom(format!("Invalid method name: {:?}", value)) - }) + MethodName::parse(value) + .ok_or_else(|| serde::de::Error::custom(format!("Invalid method name: {:?}", value))) } } diff --git a/crates/jmap/src/addressbook/get.rs b/crates/jmap/src/addressbook/get.rs index d5e1465d..2a775ff4 100644 --- a/crates/jmap/src/addressbook/get.rs +++ b/crates/jmap/src/addressbook/get.rs @@ -50,7 +50,11 @@ impl AddressBookGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await?; let address_book_ids = if access_token.is_member(account_id) { cache.document_ids(true).collect::() diff --git a/crates/jmap/src/blob/get.rs b/crates/jmap/src/blob/get.rs index 25ed5a76..af902776 100644 --- a/crates/jmap/src/blob/get.rs +++ b/crates/jmap/src/blob/get.rs @@ -19,8 +19,11 @@ use jmap_tools::{Map, Value}; use mail_builder::encoders::base64::base64_encode; use sha1::{Digest, Sha1}; use sha2::{Sha256, Sha512}; -use store::{ValueKey, write::{AlignedBytes, Archive}}; use std::future::Future; +use store::{ + ValueKey, + write::{AlignedBytes, Archive}, +}; use trc::AddContext; use types::{blob::BlobClass, collection::Collection, id::Id, type_state::DataType}; use utils::map::vec_map::VecMap; diff --git a/crates/jmap/src/calendar/get.rs b/crates/jmap/src/calendar/get.rs index 9d844c9e..d9edfad0 100644 --- a/crates/jmap/src/calendar/get.rs +++ b/crates/jmap/src/calendar/get.rs @@ -19,7 +19,11 @@ use jmap_proto::{ object::calendar::{self, CalendarProperty, CalendarValue, IncludeInAvailability}, }; use jmap_tools::{Key, Map, Value}; -use store::{ValueKey, roaring::RoaringBitmap, write::{AlignedBytes, Archive, ValueClass}}; +use store::{ + ValueKey, + roaring::RoaringBitmap, + write::{AlignedBytes, Archive, ValueClass}, +}; use trc::AddContext; use types::{ acl::{Acl, AclGrant}, @@ -55,7 +59,11 @@ impl CalendarGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await?; let is_owner = access_token.is_member(account_id); let calendar_ids = if is_owner { diff --git a/crates/jmap/src/contact/get.rs b/crates/jmap/src/contact/get.rs index bf312189..0abcd8d6 100644 --- a/crates/jmap/src/contact/get.rs +++ b/crates/jmap/src/contact/get.rs @@ -14,7 +14,11 @@ use jmap_proto::{ request::reference::MaybeResultReference, }; use jmap_tools::{Map, Value}; -use store::{ValueKey, roaring::RoaringBitmap, write::{AlignedBytes, Archive}}; +use store::{ + ValueKey, + roaring::RoaringBitmap, + write::{AlignedBytes, Archive}, +}; use trc::AddContext; use types::{ acl::Acl, @@ -46,7 +50,11 @@ impl ContactCardGet for Server { request.unwrap_properties(&[JSContactProperty::Id, JSContactProperty::AddressBookIds]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await?; let contact_ids = if access_token.is_member(account_id) { cache.document_ids(false).collect::() diff --git a/crates/jmap/src/file/get.rs b/crates/jmap/src/file/get.rs index 8b4e44ac..97f835cc 100644 --- a/crates/jmap/src/file/get.rs +++ b/crates/jmap/src/file/get.rs @@ -13,7 +13,11 @@ use jmap_proto::{ types::date::UTCDate, }; use jmap_tools::{Map, Value}; -use store::{ValueKey, roaring::RoaringBitmap, write::{AlignedBytes, Archive, now}}; +use store::{ + ValueKey, + roaring::RoaringBitmap, + write::{AlignedBytes, Archive, now}, +}; use trc::AddContext; use types::{ acl::{Acl, AclGrant}, @@ -45,7 +49,11 @@ impl FileNodeGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await?; let file_node_ids = if access_token.is_member(account_id) { cache diff --git a/crates/jmap/src/file/query.rs b/crates/jmap/src/file/query.rs index 7e1a1b40..9d9ff3ba 100644 --- a/crates/jmap/src/file/query.rs +++ b/crates/jmap/src/file/query.rs @@ -36,7 +36,11 @@ impl FileNodeQuery for Server { let account_id = request.account_id.document_id(); let mut filters = Vec::with_capacity(request.filter.len()); let cache = self - .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await?; for cond in std::mem::take(&mut request.filter) { diff --git a/crates/jmap/src/sieve/get.rs b/crates/jmap/src/sieve/get.rs index d07664cb..bb5d524a 100644 --- a/crates/jmap/src/sieve/get.rs +++ b/crates/jmap/src/sieve/get.rs @@ -12,8 +12,11 @@ use jmap_proto::{ object::sieve::{Sieve, SieveProperty, SieveValue}, }; use jmap_tools::{Map, Value}; -use store::{ValueKey, write::{AlignedBytes, Archive}}; use std::future::Future; +use store::{ + ValueKey, + write::{AlignedBytes, Archive}, +}; use trc::AddContext; use types::{ blob::{BlobClass, BlobId, BlobSection}, diff --git a/crates/managesieve/src/op/checkscript.rs b/crates/managesieve/src/op/checkscript.rs index 6c1a15aa..a07b453d 100644 --- a/crates/managesieve/src/op/checkscript.rs +++ b/crates/managesieve/src/op/checkscript.rs @@ -7,8 +7,8 @@ use std::time::Instant; use common::network::SessionStream; -use registry::schema::enums::Permission; use imap_proto::receiver::Request; +use registry::schema::enums::Permission; use crate::core::{Command, Session, StatusResponse}; diff --git a/crates/managesieve/src/op/deletescript.rs b/crates/managesieve/src/op/deletescript.rs index 8976b713..5b720ae2 100644 --- a/crates/managesieve/src/op/deletescript.rs +++ b/crates/managesieve/src/op/deletescript.rs @@ -6,9 +6,9 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::network::SessionStream; -use registry::schema::enums::Permission; use email::sieve::{delete::SieveScriptDelete, ingest::SieveScriptIngest}; use imap_proto::receiver::Request; +use registry::schema::enums::Permission; use std::time::Instant; use store::write::BatchBuilder; use trc::AddContext; diff --git a/crates/managesieve/src/op/getscript.rs b/crates/managesieve/src/op/getscript.rs index 64277432..5895f5cb 100644 --- a/crates/managesieve/src/op/getscript.rs +++ b/crates/managesieve/src/op/getscript.rs @@ -6,9 +6,9 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::network::SessionStream; -use registry::schema::enums::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; +use registry::schema::enums::Permission; use std::time::Instant; use store::{ ValueKey, diff --git a/crates/managesieve/src/op/renamescript.rs b/crates/managesieve/src/op/renamescript.rs index 134ad430..fef3ce17 100644 --- a/crates/managesieve/src/op/renamescript.rs +++ b/crates/managesieve/src/op/renamescript.rs @@ -6,9 +6,9 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::{network::SessionStream, storage::index::ObjectIndexBuilder}; -use registry::schema::enums::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; +use registry::schema::enums::Permission; use std::time::Instant; use store::{ ValueKey, diff --git a/crates/managesieve/src/op/setactive.rs b/crates/managesieve/src/op/setactive.rs index 5b5bbf96..1cb74cd1 100644 --- a/crates/managesieve/src/op/setactive.rs +++ b/crates/managesieve/src/op/setactive.rs @@ -7,8 +7,8 @@ use std::time::Instant; use common::network::SessionStream; -use registry::schema::enums::Permission; use imap_proto::receiver::Request; +use registry::schema::enums::Permission; use store::{SerializeInfallible, write::BatchBuilder}; use trc::AddContext; use types::{collection::Collection, field::PrincipalField}; diff --git a/crates/smtp/src/inbound/auth.rs b/crates/smtp/src/inbound/auth.rs index 457cade9..354e2c02 100644 --- a/crates/smtp/src/inbound/auth.rs +++ b/crates/smtp/src/inbound/auth.rs @@ -111,11 +111,7 @@ impl Session { .and_then(|access_token| access_token.assert_has_permission(Permission::EmailSend)); let result = match result { - Ok(access_token) => { - self.server - .account_info(access_token.account_id()) - .await - } + Ok(access_token) => self.server.account_info(access_token.account_id()).await, Err(err) => Err(err), }; diff --git a/crates/trc/src/event/enums_impl.rs b/crates/trc/src/event/enums_impl.rs index f397b50f..e7444cac 100644 --- a/crates/trc/src/event/enums_impl.rs +++ b/crates/trc/src/event/enums_impl.rs @@ -6,7 +6,7 @@ // This file is auto-generated. Do not edit directly. -use crate::{event::enums::*, Level}; +use crate::{Level, event::enums::*}; impl EventType { pub fn parse(value: &str) -> Option { @@ -4783,7 +4783,9 @@ impl EventType { EventType::Auth(AuthEvent::Success) => "Authentication error", EventType::Auth(AuthEvent::Failed) => "Authentication failed", EventType::Auth(AuthEvent::TokenExpired) => "Authentication error", - EventType::Auth(AuthEvent::MissingTotp) => "A TOTP code is required to authenticate this account. Try authenticating again using 'secret$totp_token'.", + EventType::Auth(AuthEvent::MissingTotp) => { + "A TOTP code is required to authenticate this account. Try authenticating again using 'secret$totp_token'." + } EventType::Auth(AuthEvent::TooManyAttempts) => "Too many authentication attempts", EventType::Auth(AuthEvent::ClientRegistration) => "Authentication error", EventType::Auth(AuthEvent::Error) => "Authentication error", @@ -4835,7 +4837,9 @@ impl EventType { EventType::Jmap(JmapEvent::InvalidResultReference) => "Invalid result reference", EventType::Jmap(JmapEvent::Forbidden) => "Forbidden", EventType::Jmap(JmapEvent::AccountNotFound) => "Account not found", - EventType::Jmap(JmapEvent::AccountNotSupportedByMethod) => "Account not supported by method", + EventType::Jmap(JmapEvent::AccountNotSupportedByMethod) => { + "Account not supported by method" + } EventType::Jmap(JmapEvent::AccountReadOnly) => "Account read-only", EventType::Jmap(JmapEvent::NotFound) => "Not found", EventType::Jmap(JmapEvent::CannotCalculateChanges) => "Cannot calculate changes", @@ -5004,7 +5008,9 @@ impl EventType { EventType::Smtp(SmtpEvent::UnsupportedParameter) => "SMTP error", EventType::Smtp(SmtpEvent::SyntaxError) => "SMTP error", EventType::Smtp(SmtpEvent::RequestTooLarge) => "SMTP error", - EventType::Store(StoreEvent::AssertValueFailed) => "Another process has modified the value", + EventType::Store(StoreEvent::AssertValueFailed) => { + "Another process has modified the value" + } EventType::Store(StoreEvent::FoundationdbError) => "FoundationDB error", EventType::Store(StoreEvent::MysqlError) => "MySQL error", EventType::Store(StoreEvent::PostgresqlError) => "PostgreSQL error", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index a40da9d7..f3051d54 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -37,6 +37,7 @@ services = { path = "../crates/services", features = ["test_mode", "enterprise"] pop3 = { path = "../crates/pop3", features = ["test_mode"] } smtp = { path = "../crates/smtp", features = ["test_mode", "enterprise"] } common = { path = "../crates/common", features = ["test_mode", "enterprise"] } +registry = { path = "../crates/registry" } email = { path = "../crates/email", features = ["test_mode", "enterprise"] } spam-filter = { path = "../crates/spam-filter", features = ["test_mode", "enterprise"] } migration = { path = "../crates/migration", features = ["test_mode", "enterprise"] } diff --git a/tests/src/cluster/broadcast.rs b/tests/src/cluster/broadcast.rs index bccc0073..2d0ecd3e 100644 --- a/tests/src/cluster/broadcast.rs +++ b/tests/src/cluster/broadcast.rs @@ -6,10 +6,6 @@ use super::ClusterTest; use crate::imap::idle; -use directory::backend::internal::{ - PrincipalAction, PrincipalField, PrincipalUpdate, PrincipalValue, - manage::{ManageDirectory, UpdatePrincipal}, -}; use groupware::cache::GroupwareCache; use std::net::IpAddr; use types::collection::SyncCollection; diff --git a/tests/src/cluster/mod.rs b/tests/src/cluster/mod.rs index 2b588652..4116df4c 100644 --- a/tests/src/cluster/mod.rs +++ b/tests/src/cluster/mod.rs @@ -18,11 +18,6 @@ use common::{ server::{Listeners, ServerProtocol}, telemetry::Telemetry, }, - core::BuildServer, - manager::{ - boot::build_ipc, - config::{ConfigManager, Patterns}, - }, }; use http::HttpSessionManager; use imap::core::ImapSessionManager; @@ -33,9 +28,7 @@ use pop3::Pop3SessionManager; use services::{SpawnServices, broadcast::subscriber::spawn_broadcast_subscriber}; use smtp::{SpawnQueueManager, core::SmtpSessionManager}; use std::{path::PathBuf, sync::Arc, time::Duration}; -use store::Stores; use tokio::sync::watch; -use utils::config::Config; pub mod broadcast; pub mod stress; diff --git a/tests/src/cluster/stress.rs b/tests/src/cluster/stress.rs index 060b4444..fc6ce73b 100644 --- a/tests/src/cluster/stress.rs +++ b/tests/src/cluster/stress.rs @@ -6,7 +6,6 @@ use crate::jmap::{assert_is_empty, mail::mailbox::destroy_all_mailboxes_no_wait, wait_for_index}; use common::Server; -use directory::backend::internal::manage::ManageDirectory; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, message::metadata::MessageData, diff --git a/tests/src/directory/imap.rs b/tests/src/directory/imap.rs deleted file mode 100644 index 7015941a..00000000 --- a/tests/src/directory/imap.rs +++ /dev/null @@ -1,194 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use std::sync::Arc; - -use common::listener::limiter::{ConcurrencyLimiter, InFlight}; -use directory::QueryParams; -use mail_parser::decoders::base64::base64_decode; -use mail_send::Credentials; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream}, - sync::watch, -}; -use tokio_rustls::TlsAcceptor; - -use crate::directory::{DirectoryTest, Item, LookupResult}; - -use super::dummy_tls_acceptor; - -#[tokio::test] -async fn imap_directory() { - // Enable logging - /*tracing::subscriber::set_global_default( - tracing_subscriber::FmtSubscriber::builder() - .with_max_level(tracing::Level::DEBUG) - .finish(), - ) - .unwrap();*/ - - // Spawn mock LMTP server - let shutdown = spawn_mock_imap_server(5); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - - // Obtain directory handle - let mut config = DirectoryTest::new(None).await; - let handle = config.directories.directories.remove("imap").unwrap(); - - // Basic lookup - let tests = vec![ - ( - Item::Authenticate(Credentials::Plain { - username: "john".to_string(), - secret: "ok".to_string(), - }), - LookupResult::True, - ), - ( - Item::Authenticate(Credentials::Plain { - username: "john".to_string(), - secret: "bad".to_string(), - }), - LookupResult::False, - ), - ]; - - for (item, expected) in &tests { - assert_eq!( - &LookupResult::from( - handle - .query( - QueryParams::credentials(item.as_credentials()).with_return_member_of(true) - ) - .await - .unwrap() - .is_some() - ), - expected - ); - } - - // Concurrent requests - let mut requests = Vec::new(); - for n in 0..10 { - let (item, expected) = &tests[n % tests.len()]; - let item = item.append(n); - let item_clone = item.clone(); - let handle = handle.clone(); - requests.push(( - tokio::spawn(async move { - LookupResult::from( - handle - .query( - QueryParams::credentials(item.as_credentials()) - .with_return_member_of(true), - ) - .await - .unwrap() - .is_some(), - ) - }), - item_clone, - expected.append(n), - )); - } - for (result, item, expected_result) in requests { - assert_eq!( - result.await.unwrap(), - expected_result, - "Failed for {item:?}" - ); - } - - // Shutdown - shutdown.send(false).ok(); -} - -pub fn spawn_mock_imap_server(max_concurrency: u64) -> watch::Sender { - let (tx, mut rx) = watch::channel(true); - - tokio::spawn(async move { - let listener = TcpListener::bind("127.0.0.1:9198") - .await - .unwrap_or_else(|e| { - panic!("Failed to bind mock IMAP server to 127.0.0.1:9198: {e}"); - }); - let acceptor = dummy_tls_acceptor(); - let limited = ConcurrencyLimiter::new(max_concurrency); - loop { - tokio::select! { - stream = listener.accept() => { - match stream { - Ok((stream, _)) => { - //println!("--- Accepted connection --- "); - let acceptor = acceptor.clone(); - let in_flight = limited.is_allowed(); - tokio::spawn(accept_imap(stream, acceptor, in_flight.into())); - } - Err(err) => { - panic!("Something went wrong: {err}" ); - } - } - }, - _ = rx.changed() => { - break; - } - }; - } - }); - - tx -} - -async fn accept_imap(stream: TcpStream, acceptor: Arc, in_flight: Option) { - let mut stream = acceptor.accept(stream).await.unwrap(); - stream - .write_all(b"* OK Clueless host service ready\r\n") - .await - .unwrap(); - - if in_flight.is_none() { - eprintln!("WARNING: Concurrency exceeded!"); - } - - let mut buf_u8 = vec![0u8; 1024]; - - while let Ok(br) = stream.read(&mut buf_u8).await { - let buf = std::str::from_utf8(&buf_u8[0..br]).unwrap(); - let (op, buf) = buf.split_once(' ').unwrap(); - - //print!("-> {}", buf); - let response = if buf.starts_with("CAPABILITY") { - format!( - "* CAPABILITY IMAP4rev2 IMAP4rev1 AUTH=PLAIN\r\n{op} OK CAPABILITY completed\r\n", - ) - } else if buf.starts_with("NOOP") { - format!("{op} OK NOOP completed\r\n") - } else if buf.starts_with("AUTHENTICATE PLAIN") { - let buf = base64_decode(buf.rsplit_once(' ').unwrap().1.as_bytes()).unwrap(); - if String::from_utf8_lossy(&buf).contains("ok") { - format!("{op} OK Great success!\r\n") - } else { - format!("{op} BAD No soup for you!\r\n") - } - } else if buf.starts_with("LOGOUT") { - format!("* BYE\r\n{op} OK LOGOUT completed\r\n") - } else { - panic!("Unknown command: {}", buf.trim()); - }; - //print!("<- {}", response); - for line in response.split_inclusive('\n') { - stream.write_all(line.as_bytes()).await.unwrap(); - stream.flush().await.unwrap(); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - - if buf.contains("bye") || buf.starts_with("LOGOUT") { - return; - } - } -} diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs index b60d092b..701cea73 100644 --- a/tests/src/directory/internal.rs +++ b/tests/src/directory/internal.rs @@ -12,18 +12,6 @@ use crate::{ }; use ahash::AHashSet; use common::{Core, Inner, Server, config::storage::Storage}; -use directory::{ - Permission, QueryBy, QueryParams, Type, - backend::{ - RcptType, - internal::{ - PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue, - lookup::DirectoryStore, - manage::{self, ChangedPrincipals, ManageDirectory, UpdatePrincipal}, - }, - }, -}; -use http::management::stores::destroy_account_data; use mail_send::Credentials; use store::{ IterateParams, Store, ValueKey, diff --git a/tests/src/directory/ldap.rs b/tests/src/directory/ldap.rs index 2cc0fb1b..e0ba577e 100644 --- a/tests/src/directory/ldap.rs +++ b/tests/src/directory/ldap.rs @@ -4,17 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Debug; - -use directory::{ - QueryParams, ROLE_USER, Type, - backend::{RcptType, internal::manage::ManageDirectory}, -}; -use mail_send::Credentials; - use crate::directory::{ DirectoryTest, IntoTestPrincipal, TestPrincipal, map_account_id, map_account_ids, }; +use mail_send::Credentials; +use std::fmt::Debug; #[tokio::test] async fn ldap_directory() { diff --git a/tests/src/directory/mod.rs b/tests/src/directory/mod.rs index d80cdcb8..60ede0f5 100644 --- a/tests/src/directory/mod.rs +++ b/tests/src/directory/mod.rs @@ -4,28 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -pub mod imap; pub mod internal; pub mod ldap; pub mod oidc; -pub mod smtp; pub mod sql; +use crate::{AssertConfig, store::TempDir}; use common::{Core, Server, config::smtp::session::AddressMapping}; -use directory::{ - Directories, Principal, PrincipalData, Type, - backend::internal::{PrincipalField, PrincipalSet, manage::ManageDirectory}, -}; use mail_send::Credentials; use rustls::ServerConfig; use rustls_pemfile::{certs, pkcs8_private_keys}; use rustls_pki_types::PrivateKeyDer; use std::{borrow::Cow, io::BufReader, sync::Arc}; -use store::{Store, Stores}; use tokio_rustls::TlsAcceptor; -use crate::{AssertConfig, store::TempDir}; - const CONFIG: &str = r#" [directory."rocksdb"] type = "internal" diff --git a/tests/src/directory/oidc.rs b/tests/src/directory/oidc.rs index 93929ca6..979700da 100644 --- a/tests/src/directory/oidc.rs +++ b/tests/src/directory/oidc.rs @@ -13,7 +13,6 @@ use crate::{ http_server::{HttpMessage, spawn_mock_http_server}, }; use base64::{Engine, engine::general_purpose}; -use directory::QueryParams; use http_proto::{JsonProblemResponse, JsonResponse, ToHttpResponse}; use hyper::{Method, StatusCode}; use mail_send::Credentials; diff --git a/tests/src/directory/smtp.rs b/tests/src/directory/smtp.rs deleted file mode 100644 index 15bf34a9..00000000 --- a/tests/src/directory/smtp.rs +++ /dev/null @@ -1,348 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use super::dummy_tls_acceptor; -use crate::directory::{DirectoryTest, Item, LookupResult}; -use common::listener::limiter::{ConcurrencyLimiter, InFlight}; -use directory::{QueryParams, backend::RcptType}; -use mail_parser::decoders::base64::base64_decode; -use mail_send::Credentials; -use std::sync::Arc; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream}, - sync::watch, -}; -use tokio_rustls::TlsAcceptor; - -#[tokio::test] -async fn lmtp_directory() { - // Spawn mock LMTP server - let shutdown = spawn_mock_lmtp_server(5); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - - // Obtain directory handle - let mut config = DirectoryTest::new(None).await; - let handle = config.directories.directories.remove("smtp").unwrap(); - let core = config.server; - - // Basic lookup - let tests = vec![ - (Item::IsAccount("john-ok@domain".into()), LookupResult::True), - ( - Item::IsAccount("john-bad@domain".into()), - LookupResult::False, - ), - ( - Item::Verify("john-ok@domain".into()), - LookupResult::Values(vec!["john-ok@domain".into()]), - ), - ( - Item::Verify("doesnot@exist.org".into()), - LookupResult::False, - ), - ( - Item::Expand("sales-ok,item1,item2,item3".into()), - LookupResult::Values(vec![ - "sales-ok".into(), - "item1".into(), - "item2".into(), - "item3".into(), - ]), - ), - (Item::Expand("other".into()), LookupResult::False), - ( - Item::Authenticate(Credentials::Plain { - username: "john".into(), - secret: "ok".into(), - }), - LookupResult::True, - ), - ( - Item::Authenticate(Credentials::Plain { - username: "john".into(), - secret: "bad".into(), - }), - LookupResult::False, - ), - ]; - - for (item, expected) in &tests { - let result: LookupResult = match item { - Item::IsAccount(v) => { - (core.rcpt(&handle, v, 0).await.unwrap() == RcptType::Mailbox).into() - } - Item::Authenticate(v) => handle - .query(QueryParams::credentials(v).with_return_member_of(true)) - .await - .unwrap() - .is_some() - .into(), - Item::Verify(v) => match core.vrfy(&handle, v, 0).await { - Ok(v) => v.into(), - Err(e) => { - if e.matches(trc::EventType::Store(trc::StoreEvent::NotSupported)) { - LookupResult::False - } else { - panic!("Unexpected error: {e:?}") - } - } - }, - Item::Expand(v) => match core.expn(&handle, v, 0).await { - Ok(v) => v.into(), - Err(e) => { - if e.matches(trc::EventType::Store(trc::StoreEvent::NotSupported)) { - LookupResult::False - } else { - panic!("Unexpected error: {e:?}") - } - } - }, - }; - - assert_eq!(&result, expected); - } - - // Concurrent requests - let mut requests = Vec::new(); - let core = Arc::new(core); - for n in 0..100 { - let (item, expected) = &tests[n % tests.len()]; - let item = item.append(n); - let item_clone = item.clone(); - let handle = handle.clone(); - let core = core.clone(); - requests.push(( - tokio::spawn(async move { - let result: LookupResult = match &item { - Item::IsAccount(v) => { - (core.rcpt(&handle, v, 0).await.unwrap() == RcptType::Mailbox).into() - } - Item::Authenticate(v) => handle - .query(QueryParams::credentials(v).with_return_member_of(true)) - .await - .unwrap() - .is_some() - .into(), - Item::Verify(v) => match core.vrfy(&handle, v, 0).await { - Ok(v) => v.into(), - Err(e) => { - if e.matches(trc::EventType::Store(trc::StoreEvent::NotSupported)) { - LookupResult::False - } else { - panic!("Unexpected error: {e:?}") - } - } - }, - Item::Expand(v) => match core.expn(&handle, v, 0).await { - Ok(v) => v.into(), - Err(e) => { - if e.matches(trc::EventType::Store(trc::StoreEvent::NotSupported)) { - LookupResult::False - } else { - panic!("Unexpected error: {e:?}") - } - } - }, - }; - - result - }), - item_clone, - expected.append(n), - )); - } - for (result, item, expected_result) in requests { - assert_eq!( - result.await.unwrap(), - expected_result, - "Failed for {item:?}" - ); - } - - // Shutdown - shutdown.send(false).ok(); - - // Verify that caching works - TcpStream::connect("127.0.0.1:9199").await.unwrap_err(); - - let mut requests = Vec::new(); - for n in 0..100 { - let (item, expected) = &tests[n % tests.len()]; - if matches!(item, Item::IsAccount(_)) { - let item = item.append(n); - let item_clone = item.clone(); - let handle = handle.clone(); - let core = core.clone(); - requests.push(( - tokio::spawn(async move { - let result: LookupResult = match &item { - Item::IsAccount(v) => { - (core.rcpt(&handle, v, 0).await.unwrap() == RcptType::Mailbox).into() - } - _ => unreachable!(), - }; - - result - }), - item_clone, - expected.append(n), - )); - } - } - assert!(!requests.is_empty()); - for (result, item, expected_result) in requests { - assert_eq!( - result.await.unwrap(), - expected_result, - "Failed for {item:?}" - ); - } -} - -pub fn spawn_mock_lmtp_server(max_concurrency: u64) -> watch::Sender { - let (tx, rx) = watch::channel(true); - - tokio::spawn(async move { - let listener = TcpListener::bind("127.0.0.1:9199") - .await - .unwrap_or_else(|e| { - panic!("Failed to bind mock SMTP server to 127.0.0.1:9199: {e}"); - }); - let acceptor = dummy_tls_acceptor(); - let limited = ConcurrencyLimiter::new(max_concurrency); - let mut rx_ = rx.clone(); - loop { - tokio::select! { - stream = listener.accept() => { - match stream { - Ok((stream, _)) => { - let acceptor = acceptor.clone(); - let in_flight = limited.is_allowed(); - tokio::spawn(accept_smtp(stream, rx.clone(), acceptor, in_flight.into())); - } - Err(err) => { - panic!("Something went wrong: {err}" ); - } - } - }, - _ = rx_.changed() => { - break; - } - }; - } - }); - - tx -} - -async fn accept_smtp( - stream: TcpStream, - mut rx: watch::Receiver, - acceptor: Arc, - in_flight: Option, -) { - let mut stream = acceptor.accept(stream).await.unwrap(); - stream - .write_all(b"220 [127.0.0.1] Clueless host service ready\r\n") - .await - .unwrap(); - - if in_flight.is_none() { - eprintln!("WARNING: Concurrency exceeded!"); - } - - let mut buf_u8 = vec![0u8; 1024]; - - loop { - let br = tokio::select! { - br = stream.read(&mut buf_u8) => { - match br { - Ok(br) => { - br - } - Err(_) => { - break; - } - } - }, - _ = rx.changed() => { - break; - } - }; - - let buf = std::str::from_utf8(&buf_u8[0..br]).unwrap(); - let response = if buf.starts_with("LHLO") { - "250-mx.foobar.org\r\n250 AUTH PLAIN\r\n".into() - } else if buf.starts_with("MAIL FROM") { - if buf.contains("<>") || buf.contains("ok@") { - "250 OK\r\n".into() - } else { - "552-I do not\r\n552 like that MAIL FROM.\r\n".into() - } - } else if buf.starts_with("RCPT TO") { - if buf.contains("ok") { - "250 OK\r\n".into() - } else { - "550-I refuse to\r\n550 accept that recipient.\r\n".into() - } - } else if buf.starts_with("VRFY") { - if buf.contains("ok") { - format!("250 {}\r\n", buf.split_once(' ').unwrap().1) - } else { - "550-I refuse to\r\n550 verify that recipient.\r\n".into() - } - } else if buf.starts_with("EXPN") { - if buf.contains("ok") { - let parts = buf - .split_once(' ') - .unwrap() - .1 - .split(',') - .filter_map(|s| { - if !s.is_empty() { - s.to_string().into() - } else { - None - } - }) - .collect::>(); - let mut buf = String::with_capacity(16); - for (pos, part) in parts.iter().enumerate() { - buf.push_str("250"); - buf.push(if pos == parts.len() - 1 { ' ' } else { '-' }); - buf.push_str(part); - buf.push_str("\r\n"); - } - - buf - } else { - "550-I refuse to\r\n550 accept that recipient.\r\n".into() - } - } else if buf.starts_with("AUTH PLAIN") { - let buf = base64_decode(buf.rsplit_once(' ').unwrap().1.as_bytes()).unwrap(); - if String::from_utf8_lossy(&buf).contains("ok") { - "235 Great success!\r\n".into() - } else { - "535 No soup for you\r\n".into() - } - } else if buf.starts_with("NOOP") { - "250 Siesta time\r\n".into() - } else if buf.starts_with("QUIT") { - "250 Arrivederci!\r\n".into() - } else if buf.starts_with("RSET") { - "250 Your wish is my command.\r\n".into() - } else { - panic!("Unknown command: {}", buf.trim()); - }; - //print!("<- {}", response); - stream.write_all(response.as_bytes()).await.unwrap(); - - if buf.contains("bye") || buf.starts_with("QUIT") { - return; - } - } -} diff --git a/tests/src/directory/sql.rs b/tests/src/directory/sql.rs index c71265cc..7785a3fb 100644 --- a/tests/src/directory/sql.rs +++ b/tests/src/directory/sql.rs @@ -4,10 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use directory::{ - QueryParams, ROLE_ADMIN, ROLE_USER, Type, - backend::{RcptType, internal::manage::ManageDirectory}, -}; use mail_send::Credentials; #[allow(unused_imports)] diff --git a/tests/src/imap/acl.rs b/tests/src/imap/acl.rs index 838d9515..c93732b9 100644 --- a/tests/src/imap/acl.rs +++ b/tests/src/imap/acl.rs @@ -4,9 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::jmap::mail::delivery::SmtpConnection; - use super::{AssertResult, ImapConnection, Type, append::assert_append_message}; +use crate::jmap::mail::delivery::SmtpConnection; use imap_proto::ResponseType; pub async fn test(mut imap_john: &mut ImapConnection, _imap_check: &mut ImapConnection) { diff --git a/tests/src/imap/basic.rs b/tests/src/imap/basic.rs index 921b3af4..5acdb58e 100644 --- a/tests/src/imap/basic.rs +++ b/tests/src/imap/basic.rs @@ -4,21 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::auth::sasl::sasl_decode_challenge_oauth; +use super::{AssertResult, ImapConnection, Type}; +use directory::Credentials; use imap_proto::ResponseType; use mail_parser::decoders::base64::base64_decode; -use mail_send::Credentials; - -use super::{AssertResult, ImapConnection, Type}; pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) { println!("Running basic tests..."); // Test OAuth Bearer decoding assert!( - Credentials::OAuthBearer { - token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==".to_string() - } == sasl_decode_challenge_oauth( + Credentials::Bearer { + token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==".to_string(), + username: None, + } == Credentials::decode_sasl_challenge_oauth( &base64_decode( concat!( "bixhPXVzZXJAZXhhbXBsZS5jb20sAWhv", diff --git a/tests/src/imap/mod.rs b/tests/src/imap/mod.rs index 9bac41ee..a9a78b66 100644 --- a/tests/src/imap/mod.rs +++ b/tests/src/imap/mod.rs @@ -29,7 +29,6 @@ use crate::{ }, }; use ::managesieve::core::ManageSieveSessionManager; -use ::store::Stores; use ahash::AHashSet; use base64::{Engine, engine::general_purpose}; use common::{ @@ -38,7 +37,6 @@ use common::{ server::{Listeners, ServerProtocol}, telemetry::Telemetry, }, - core::BuildServer, manager::boot::build_ipc, }; use http::HttpSessionManager; @@ -57,7 +55,6 @@ use tokio::{ net::TcpStream, sync::watch, }; -use utils::config::Config; #[tokio::test] pub async fn imap_tests() { diff --git a/tests/src/jmap/auth/limits.rs b/tests/src/jmap/auth/limits.rs index 9914f1a9..d6b2b5e4 100644 --- a/tests/src/jmap/auth/limits.rs +++ b/tests/src/jmap/auth/limits.rs @@ -7,10 +7,8 @@ use crate::{ directory::internal::TestInternalDirectory, imap::{ImapConnection, Type}, - jmap::{JMAPTest}, + jmap::JMAPTest, }; -use common::listener::blocked::BLOCKED_IP_KEY; -use directory::Permission; use imap_proto::ResponseType; use jmap_client::{ client::{Client, Credentials}, diff --git a/tests/src/jmap/auth/permissions.rs b/tests/src/jmap/auth/permissions.rs index e98f2859..6993ec64 100644 --- a/tests/src/jmap/auth/permissions.rs +++ b/tests/src/jmap/auth/permissions.rs @@ -9,11 +9,6 @@ use crate::{ jmap::{JMAPTest, ManagementApi, server::List}, }; use ahash::AHashSet; -use common::auth::{AccessToken, TenantInfo}; -use directory::{ - Permission, Type, - backend::internal::{PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue}, -}; use email::message::delivery::{IngestMessage, IngestRecipient, LocalDeliveryStatus, MailDelivery}; use std::sync::Arc; diff --git a/tests/src/jmap/auth/quota.rs b/tests/src/jmap/auth/quota.rs index 1ef6b338..bf39e14b 100644 --- a/tests/src/jmap/auth/quota.rs +++ b/tests/src/jmap/auth/quota.rs @@ -12,7 +12,6 @@ use crate::{ }; use common::config::smtp::queue::QueueName; use email::{cache::MessageCacheFetch, mailbox::INBOX_ID}; -use http::management::stores::recalculate_quota; use jmap::blob::upload::DISABLE_UPLOAD_QUOTA; use jmap_client::{ core::set::{SetErrorType, SetObject}, diff --git a/tests/src/jmap/core/push_subscription.rs b/tests/src/jmap/core/push_subscription.rs index 682b8f3e..26f54a73 100644 --- a/tests/src/jmap/core/push_subscription.rs +++ b/tests/src/jmap/core/push_subscription.rs @@ -6,7 +6,7 @@ use crate::{AssertConfig, add_test_certs, jmap::JMAPTest}; use base64::{Engine, engine::general_purpose}; -use common::{Caches, Core, Data, Inner, config::server::Listeners, listener::SessionData}; +use common::{Caches, Core, Data, Inner, config::server::Listeners, network::SessionData}; use ece::EcKeyComponents; use http_proto::{HtmlResponse, ToHttpResponse, request::fetch_body}; use hyper::{StatusCode, body, header::CONTENT_ENCODING, server::conn::http1, service::service_fn}; @@ -24,7 +24,6 @@ use std::{ use store::ahash::AHashSet; use tokio::sync::mpsc; use types::{id::Id, type_state::DataType}; -use utils::{config::Config, map::vec_map::VecMap}; const SERVER: &str = r#" [server] diff --git a/tests/src/jmap/mail/crypto.rs b/tests/src/jmap/mail/crypto.rs index 7c151772..3f9f4a23 100644 --- a/tests/src/jmap/mail/crypto.rs +++ b/tests/src/jmap/mail/crypto.rs @@ -5,9 +5,6 @@ */ use crate::jmap::{JMAPTest, ManagementApi, mail::delivery::SmtpConnection}; -use email::message::crypto::{ - Algorithm, EncryptMessage, EncryptionMethod, EncryptionParams, EncryptionType, try_parse_certs, -}; use mail_parser::{MessageParser, MimeHeaders}; use std::path::PathBuf; use store::{ diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 60cc7f44..04819ccd 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -27,11 +27,6 @@ use common::{ server::{Listeners, ServerProtocol}, telemetry::Telemetry, }, - core::BuildServer, - manager::{ - boot::build_ipc, - config::{ConfigManager, Patterns}, - }, }; use http::HttpSessionManager; use hyper::{Method, header::AUTHORIZATION}; @@ -43,10 +38,7 @@ use pop3::Pop3SessionManager; use reqwest::header; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::{Value, json}; -use services::{ - SpawnServices, - task_manager::{Task, TaskAction}, -}; +use services::SpawnServices; use smtp::{SpawnQueueManager, core::SmtpSessionManager}; use std::{ fmt::{Debug, Display}, @@ -55,12 +47,11 @@ use std::{ time::Duration, }; use store::{ - IterateParams, SUBSPACE_TASK_QUEUE, Stores, U32_LEN, U64_LEN, - write::{AnyKey, TaskEpoch, key::DeserializeBigEndian}, + IterateParams, SUBSPACE_TASK_QUEUE, U32_LEN, U64_LEN, + write::{AnyKey, key::DeserializeBigEndian}, }; use tokio::sync::watch; use types::id::Id; -use utils::config::Config; pub mod auth; pub mod calendar; @@ -152,15 +143,6 @@ pub struct JMAPTest { shutdown_tx: watch::Sender, } -pub struct Account { - name: &'static str, - secret: &'static str, - emails: &'static [&'static str], - id: Id, - id_string: String, - client: Client, -} - impl JMAPTest { pub fn account(&self, name: &str) -> &Account { self.accounts.get(name).unwrap() @@ -171,112 +153,6 @@ impl JMAPTest { } } -impl Account { - pub fn id(&self) -> &Id { - &self.id - } - - pub fn id_string(&self) -> &str { - &self.id_string - } - - pub fn client(&self) -> &Client { - &self.client - } - - pub fn name(&self) -> &'static str { - self.name - } - pub fn secret(&self) -> &'static str { - self.secret - } - - pub fn emails(&self) -> &'static [&'static str] { - self.emails - } - - pub async fn client_owned(&self) -> Client { - Client::new() - .credentials(Credentials::basic(self.name(), self.secret())) - .timeout(Duration::from_secs(3600)) - .accept_invalid_certs(true) - .follow_redirects(["127.0.0.1"]) - .connect("https://127.0.0.1:8899") - .await - .unwrap() - } -} - -pub async fn wait_for_index(server: &Server) { - let mut count = 0; - loop { - let mut has_index_tasks = None; - server - .core - .storage - .data - .iterate( - IterateParams::new( - AnyKey { - subspace: SUBSPACE_TASK_QUEUE, - key: vec![0u8], - }, - AnyKey { - subspace: SUBSPACE_TASK_QUEUE, - key: vec![u8::MAX; 16], - }, - ) - .ascending(), - |key, value| { - has_index_tasks = Some( - Task::::deserialize(key, value).unwrap_or_else(|_| Task { - due: TaskEpoch::from_inner( - key.deserialize_be_u64(key.len() - U64_LEN).unwrap(), - ), - account_id: key.deserialize_be_u32(U64_LEN).unwrap(), - document_id: key.deserialize_be_u32(U64_LEN + U32_LEN + 1).unwrap(), - action: TaskAction::SendImip, - }), - ); - - Ok(false) - }, - ) - .await - .unwrap(); - - if let Some(task) = has_index_tasks { - count += 1; - if count % 10 == 0 { - println!("Waiting for pending task {:?}...", task); - } - tokio::time::sleep(Duration::from_millis(300)).await; - } else { - break; - } - } -} - -pub async fn assert_is_empty(server: &Server) { - // Wait for pending index tasks - wait_for_index(server).await; - - // Assert is empty - store_assert_is_empty(server.store(), server.core.storage.blob.clone(), false).await; - search_store_destroy(server.search_store()).await; - - // Clean caches - for cache in [ - &server.inner.cache.events, - &server.inner.cache.contacts, - &server.inner.cache.files, - &server.inner.cache.scheduling, - ] { - cache.clear(); - } - server.inner.cache.messages.clear(); -} - async fn init_jmap_tests(delete_if_exists: bool) -> JMAPTest { // Load and parse config let temp_dir = TempDir::new("jmap_tests", delete_if_exists); @@ -482,619 +358,6 @@ async fn init_jmap_tests(delete_if_exists: bool) -> JMAPTest { } } -pub struct JmapResponse(pub Value); - -impl Account { - pub async fn jmap_get( - &self, - object: impl Display, - properties: impl IntoIterator, - ids: impl IntoIterator, - ) -> JmapResponse { - self.jmap_get_account(self, object, properties, ids).await - } - - pub async fn jmap_get_account( - &self, - account: &Account, - object: impl Display, - properties: impl IntoIterator, - ids: impl IntoIterator, - ) -> JmapResponse { - let ids = ids - .into_iter() - .map(|id| Value::String(id.to_string())) - .collect::>(); - self.jmap_method_calls(json!([[ - format!("{object}/get"), - { - "accountId": account.id_string(), - "properties": properties - .into_iter() - .map(|p| Value::String(p.to_string())) - .collect::>(), - "ids": if !ids.is_empty() { Some(ids) } else { None } - }, - "0" - ]])) - .await - } - - pub async fn jmap_query( - &self, - object: impl Display, - filter: impl IntoIterator)>, - sort_by: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - let filter = filter - .into_iter() - .map(|(k, v)| (k.to_string(), v.into())) - .collect::>(); - let sort_by = sort_by - .into_iter() - .map(|id| { - json! ({ - "property": id.to_string() - }) - }) - .collect::>(); - let arguments = [ - ("filter".to_string(), Value::Object(filter)), - ("sort".to_string(), Value::Array(sort_by)), - ] - .into_iter() - .chain( - arguments - .into_iter() - .map(|(k, v)| (k.to_string(), v.into())), - ) - .collect::>(); - - self.jmap_method_calls(json!([[format!("{object}/query"), arguments, "0"]])) - .await - } - - pub async fn jmap_create( - &self, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - self.jmap_create_account(self, object, items, arguments) - .await - } - - pub async fn jmap_create_account( - &self, - account: &Account, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - let create = items - .into_iter() - .enumerate() - .map(|(i, item)| (format!("i{i}"), item)) - .collect::>(); - let arguments = [ - ( - "accountId".to_string(), - Value::String(account.id_string().to_string()), - ), - ("create".to_string(), Value::Object(create)), - ] - .into_iter() - .chain( - arguments - .into_iter() - .map(|(k, v)| (k.to_string(), v.into())), - ) - .collect::>(); - - self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) - .await - } - - pub async fn jmap_update( - &self, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - self.jmap_update_account(self, object, items, arguments) - .await - } - - pub async fn jmap_update_account( - &self, - account: &Account, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - let update = items - .into_iter() - .map(|(i, item)| (i.to_string(), item)) - .collect::>(); - let arguments = [ - ( - "accountId".to_string(), - Value::String(account.id_string().to_string()), - ), - ("update".to_string(), Value::Object(update)), - ] - .into_iter() - .chain( - arguments - .into_iter() - .map(|(k, v)| (k.to_string(), v.into())), - ) - .collect::>(); - - self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) - .await - } - - pub async fn jmap_destroy( - &self, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - self.jmap_destroy_account(self, object, items, arguments) - .await - } - - pub async fn jmap_destroy_account( - &self, - account: &Account, - object: impl Display, - items: impl IntoIterator, - arguments: impl IntoIterator)>, - ) -> JmapResponse { - let destroy = items - .into_iter() - .map(|id| Value::String(id.to_string())) - .collect::>(); - let arguments = [ - ( - "accountId".to_string(), - Value::String(account.id_string().to_string()), - ), - ("destroy".to_string(), Value::Array(destroy)), - ] - .into_iter() - .chain( - arguments - .into_iter() - .map(|(k, v)| (k.to_string(), v.into())), - ) - .collect::>(); - - self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) - .await - } - - pub async fn jmap_copy( - &self, - from_account: &Account, - to_account: &Account, - object: impl Display, - items: impl IntoIterator, - on_success_destroy: bool, - ) -> JmapResponse { - self.jmap_method_calls(json!([[ - format!("{object}/copy"), - { - "fromAccountId": from_account.id_string(), - "accountId": to_account.id_string(), - "onSuccessDestroyOriginal": on_success_destroy, - "create": items - .into_iter() - .map(|(i, item)| (i.to_string(), item)).collect::>() - }, - "0" - ]])) - .await - } - - pub async fn jmap_changes(&self, object: impl Display, state: impl Display) -> JmapResponse { - self.jmap_method_calls(json!([[ - format!("{object}/changes"), - { - "sinceState": state.to_string() - }, - "0" - ]])) - .await - } - - pub async fn jmap_method_call(&self, method_name: &str, body: Value) -> JmapResponse { - self.jmap_method_calls(json!([[method_name, body, "0"]])) - .await - } - - pub async fn jmap_method_calls(&self, calls: Value) -> JmapResponse { - let mut headers = header::HeaderMap::new(); - - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_str(&format!( - "Basic {}", - general_purpose::STANDARD.encode(format!("{}:{}", self.name(), self.secret())) - )) - .unwrap(), - ); - - let body = json!({ - "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:quota" ], - "methodCalls": calls - }); - - JmapResponse( - serde_json::from_slice( - &reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .timeout(Duration::from_millis(1000)) - .default_headers(headers) - .build() - .unwrap() - .post("https://127.0.0.1:8899/jmap") - .body(body.to_string()) - .send() - .await - .unwrap() - .bytes() - .await - .unwrap(), - ) - .unwrap(), - ) - } - - pub async fn jmap_session_object(&self) -> JmapResponse { - let mut headers = header::HeaderMap::new(); - - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_str(&format!( - "Basic {}", - general_purpose::STANDARD.encode(format!("{}:{}", self.name(), self.secret())) - )) - .unwrap(), - ); - - JmapResponse( - serde_json::from_slice( - &reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .timeout(Duration::from_millis(1000)) - .default_headers(headers) - .build() - .unwrap() - .get("https://127.0.0.1:8899/jmap/session") - .send() - .await - .unwrap() - .bytes() - .await - .unwrap(), - ) - .unwrap(), - ) - } - - pub async fn destroy_all_addressbooks(&self) { - self.jmap_method_calls(json!([[ - "AddressBook/get", - { - "ids" : (), - "properties" : [ - "id" - ] - }, - "R1" - ], - [ - "AddressBook/set", - { - "#destroy" : { - "resultOf": "R1", - "name": "AddressBook/get", - "path": "/list/*/id" - }, - "onDestroyRemoveContents" : true - }, - "R2" - ] - ])) - .await; - } - - pub async fn destroy_all_calendars(&self) { - self.jmap_method_calls(json!([[ - "Calendar/get", - { - "ids" : (), - "properties" : [ - "id" - ] - }, - "R1" - ], - [ - "Calendar/set", - { - "#destroy" : { - "resultOf": "R1", - "name": "Calendar/get", - "path": "/list/*/id" - }, - "onDestroyRemoveEvents" : true - }, - "R2" - ] - ])) - .await; - } - - pub async fn destroy_all_event_notifications(&self) { - self.jmap_method_calls(json!([[ - "CalendarEventNotification/get", - { - "ids" : (), - "properties" : [ - "id" - ] - }, - "R1" - ], - [ - "CalendarEventNotification/set", - { - "#destroy" : { - "resultOf": "R1", - "name": "CalendarEventNotification/get", - "path": "/list/*/id" - } - }, - "R2" - ] - ])) - .await; - } -} - -impl JmapResponse { - pub fn created(&self, item_idx: u32) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/created/i{item_idx}")) - .unwrap_or_else(|| panic!("Missing created item {item_idx}: {self:?}")) - } - - pub fn not_created(&self, item_idx: u32) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/notCreated/i{item_idx}")) - .unwrap_or_else(|| panic!("Missing not created item {item_idx}: {self:?}")) - } - - pub fn updated(&self, id: &str) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/updated/{id}")) - .unwrap_or_else(|| panic!("Missing updated item {id}: {self:?}")) - } - - pub fn not_updated(&self, id: &str) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/notUpdated/{id}")) - .unwrap_or_else(|| panic!("Missing not updated item {id}: {self:?}")) - } - - pub fn copied(&self, id: &str) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/created/{id}")) - .unwrap_or_else(|| panic!("Missing updated item {id}: {self:?}")) - } - - pub fn method_response(&self) -> &Value { - self.0 - .pointer("/methodResponses/0/1") - .unwrap_or_else(|| panic!("Missing method response in response: {self:?}")) - } - - pub fn list_array(&self) -> &Value { - self.0 - .pointer("/methodResponses/0/1/list") - .unwrap_or_else(|| panic!("Missing list in response: {self:?}")) - } - - pub fn list(&self) -> &[Value] { - self.0 - .pointer("/methodResponses/0/1/list") - .and_then(|v| v.as_array()) - .unwrap_or_else(|| panic!("Missing list in response: {self:?}")) - } - - pub fn not_found(&self) -> impl Iterator { - self.0 - .pointer("/methodResponses/0/1/notFound") - .and_then(|v| v.as_array()) - .unwrap_or_else(|| panic!("Missing notFound in response: {self:?}")) - .iter() - .map(|v| v.as_str().unwrap()) - } - - pub fn ids(&self) -> impl Iterator { - self.0 - .pointer("/methodResponses/0/1/ids") - .and_then(|v| v.as_array()) - .unwrap_or_else(|| panic!("Missing ids in response: {self:?}")) - .iter() - .map(|v| v.as_str().unwrap()) - } - - pub fn destroyed(&self) -> impl Iterator { - self.0 - .pointer("/methodResponses/0/1/destroyed") - .and_then(|v| v.as_array()) - .unwrap_or_else(|| panic!("Missing destroyed in response: {self:?}")) - .iter() - .map(|v| v.as_str().unwrap()) - } - - pub fn not_destroyed(&self, id: &str) -> &Value { - self.0 - .pointer(&format!("/methodResponses/0/1/notDestroyed/{id}")) - .unwrap_or_else(|| panic!("Missing not destroyed item {id}: {self:?}")) - } - - pub fn state(&self) -> &str { - self.0 - .pointer("/methodResponses/0/1/state") - .and_then(|v| v.as_str()) - .unwrap_or_else(|| panic!("Missing state in response: {self:?}")) - } - - pub fn new_state(&self) -> &str { - self.0 - .pointer("/methodResponses/0/1/newState") - .and_then(|v| v.as_str()) - .unwrap_or_else(|| panic!("Missing new state in response: {self:?}")) - } - - pub fn changes(&self) -> impl Iterator> { - self.changes_by_type("created") - .map(ChangeType::Created) - .chain(self.changes_by_type("updated").map(ChangeType::Updated)) - .chain(self.changes_by_type("destroyed").map(ChangeType::Destroyed)) - } - - fn changes_by_type(&self, typ: &str) -> impl Iterator { - self.0 - .pointer(&format!("/methodResponses/0/1/{typ}")) - .and_then(|v| v.as_array()) - .unwrap_or_else(|| panic!("Missing {typ} changes in response: {self:?}")) - .iter() - .map(|v| v.as_str().unwrap()) - } - - pub fn pointer(&self, pointer: &str) -> Option<&Value> { - self.0.pointer(pointer) - } - - pub fn into_inner(self) -> Value { - self.0 - } -} - -pub trait JmapUtils { - fn id(&self) -> &str { - self.text_field("id") - } - - fn blob_id(&self) -> &str { - self.text_field("blobId") - } - - fn typ(&self) -> &str { - self.text_field("type") - } - - fn description(&self) -> &str { - self.text_field("description") - } - - fn with_property(self, field: impl Display, value: impl Into) -> Self; - - fn text_field(&self, field: &str) -> &str; - - fn assert_is_equal(&self, other: Value); -} - -impl JmapUtils for Value { - fn text_field(&self, field: &str) -> &str { - self.pointer(&format!("/{field}")) - .and_then(|v| v.as_str()) - .unwrap_or_else(|| panic!("Missing {field} in object: {self:?}")) - } - fn assert_is_equal(&self, expected: Value) { - if self != &expected { - panic!( - "Values are not equal:\ngot: {}\nexpected: {}", - serde_json::to_string_pretty(self).unwrap(), - serde_json::to_string_pretty(&expected).unwrap() - ); - } - } - fn with_property(mut self, field: impl Display, value: impl Into) -> Self { - if let Value::Object(map) = &mut self { - map.insert(field.to_string(), value.into()); - } else { - panic!("Not an object: {self:?}"); - } - self - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ChangeType<'x> { - Created(&'x str), - Updated(&'x str), - Destroyed(&'x str), -} - -impl<'x> ChangeType<'x> { - pub fn as_created(&self) -> &str { - match self { - ChangeType::Created(id) => id, - _ => panic!("Not a created change: {self:?}"), - } - } - - pub fn as_updated(&self) -> &str { - match self { - ChangeType::Updated(id) => id, - _ => panic!("Not an updated change: {self:?}"), - } - } - - pub fn as_destroyed(&self) -> &str { - match self { - ChangeType::Destroyed(id) => id, - _ => panic!("Not a destroyed change: {self:?}"), - } - } -} - -impl Display for JmapResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } -} - -impl Debug for JmapResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - serde_json::to_string_pretty(&self.0) - .map_err(|_| std::fmt::Error) - .and_then(|s| std::fmt::Display::fmt(&s, f)) - } -} - -pub trait IntoJmapSet { - fn into_jmap_set(self) -> Value; -} - -impl> IntoJmapSet for T { - fn into_jmap_set(self) -> Value { - Value::Object( - self.into_iter() - .map(|id| (id.to_string(), Value::Bool(true))) - .collect::>(), - ) - } -} - pub fn find_values(string: &str, name: &str) -> Vec { let mut last_pos = 0; let mut values = Vec::new(); diff --git a/tests/src/jmap/server/enterprise.rs b/tests/src/jmap/server/enterprise.rs index 08484289..4981672b 100644 --- a/tests/src/jmap/server/enterprise.rs +++ b/tests/src/jmap/server/enterprise.rs @@ -22,23 +22,12 @@ use crate::{ use common::{ Core, Server, config::telemetry::{StoreTracer, TelemetrySubscriberType}, - core::BuildServer, - enterprise::{ - Enterprise, MetricStore, TraceStore, Undelete, config::parse_metric_alerts, - license::LicenseKey, - }, + enterprise::{Enterprise, license::LicenseKey}, telemetry::{ - metrics::store::{Metric, MetricsStore, SharedMetricHistory}, + metrics::store::{MetricsStore, SharedMetricHistory}, tracers::store::TracingStore, }, }; -use directory::{QueryBy, backend::internal::manage::ManageDirectory}; -use http::management::{ - enterprise::undelete::{ - DeletedBlobResponse, DeletedItemResponse, UndeleteRequest, UndeleteResponse, - }, - stores::destroy_account_data, -}; use imap_proto::ResponseType; use nlp::language::Language; use std::{sync::Arc, time::Duration}; @@ -53,7 +42,6 @@ use trc::{ ipc::{bitset::Bitset, subscriber::SubscriberBuilder}, *, }; -use utils::config::{Config, cron::SimpleCron}; const METRICS_CONFIG: &str = r#" [metrics.alerts.expected] diff --git a/tests/src/jmap/server/purge.rs b/tests/src/jmap/server/purge.rs index c68b4e55..94832a6e 100644 --- a/tests/src/jmap/server/purge.rs +++ b/tests/src/jmap/server/purge.rs @@ -10,13 +10,11 @@ use crate::{ }; use ahash::AHashSet; use common::Server; -use directory::{QueryBy, backend::internal::manage::ManageDirectory}; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, mailbox::{INBOX_ID, JUNK_ID, TRASH_ID}, message::delete::EmailDeletion, }; -use http::management::stores::destroy_account_data; use imap_proto::ResponseType; use store::{IterateParams, LogKey, U32_LEN, U64_LEN, write::key::DeserializeBigEndian}; use types::id::Id; diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 5cd7c5c1..4edeb8c6 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -6,6 +6,8 @@ use std::path::PathBuf; +#[cfg(test)] +use ::store::registry::bootstrap::Bootstrap; #[cfg(not(target_env = "msvc"))] use jemallocator::Jemalloc; #[cfg(test)] @@ -15,13 +17,12 @@ use trc::Collector; #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; +/* #[cfg(test)] pub mod cluster; #[cfg(test)] pub mod directory; #[cfg(test)] -pub mod http_server; -#[cfg(test)] pub mod imap; #[cfg(test)] pub mod jmap; @@ -31,6 +32,11 @@ pub mod smtp; pub mod store; #[cfg(test)] pub mod webdav; +*/ +#[cfg(test)] +pub mod system; +#[cfg(test)] +pub mod utils; pub fn add_test_certs(config: &str) -> String { let mut cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -52,7 +58,7 @@ pub trait AssertConfig { } #[cfg(test)] -impl AssertConfig for utils::config::Config { +impl AssertConfig for Bootstrap { fn assert_no_errors(self) -> Self { if !self.errors.is_empty() { panic!("Errors: {:#?}", self.errors); diff --git a/tests/src/smtp/config.rs b/tests/src/smtp/config.rs index 992a19ee..f7f039b2 100644 --- a/tests/src/smtp/config.rs +++ b/tests/src/smtp/config.rs @@ -4,8 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration}; - +use super::add_test_certs; use common::{ Server, config::{ @@ -14,15 +13,10 @@ use common::{ }, expr::{functions::ResolveVariable, if_block::*, tokenizer::TokenMap, *}, }; - use compact_str::ToCompactString; -use throttle::parse_queue_rate_limiter; +use std::{fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration}; use tokio::net::TcpSocket; -use utils::config::{Config, Rate}; - -use super::add_test_certs; - struct TestEnvelope { pub local_ip: IpAddr, pub remote_ip: IpAddr, diff --git a/tests/src/smtp/inbound/antispam.rs b/tests/src/smtp/inbound/antispam.rs index 31ff78f7..dfc0eec4 100644 --- a/tests/src/smtp/inbound/antispam.rs +++ b/tests/src/smtp/inbound/antispam.rs @@ -13,7 +13,6 @@ use ahash::{AHashMap, AHashSet}; use common::{ Core, Server, auth::AccessToken, - config::spamfilter::SpamFilterAction, enterprise::{ SpamFilterLlmConfig, llm::{ @@ -57,8 +56,6 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use store::{Stores, write::BatchBuilder}; -use utils::config::Config; const CONFIG: &str = r#" [spam-filter.score] diff --git a/tests/src/smtp/inbound/auth.rs b/tests/src/smtp/inbound/auth.rs index ae0046a5..e91236b7 100644 --- a/tests/src/smtp/inbound/auth.rs +++ b/tests/src/smtp/inbound/auth.rs @@ -4,11 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::Core; - -use store::Stores; -use utils::config::Config; - use crate::{ AssertConfig, smtp::{ @@ -16,6 +11,7 @@ use crate::{ session::{TestSession, VerifyResponse}, }, }; +use common::Core; use smtp::core::{Session, State}; const CONFIG: &str = r#" diff --git a/tests/src/smtp/inbound/data.rs b/tests/src/smtp/inbound/data.rs index aaf78f75..91cddb20 100644 --- a/tests/src/smtp/inbound/data.rs +++ b/tests/src/smtp/inbound/data.rs @@ -4,10 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::Core; -use store::Stores; -use utils::config::Config; - use crate::{ AssertConfig, smtp::{ @@ -17,6 +13,7 @@ use crate::{ }, store::cleanup::store_assert_is_empty, }; +use common::Core; use smtp::core::Session; const CONFIG: &str = r#" diff --git a/tests/src/smtp/inbound/dmarc.rs b/tests/src/smtp/inbound/dmarc.rs index 768711bc..1aa271db 100644 --- a/tests/src/smtp/inbound/dmarc.rs +++ b/tests/src/smtp/inbound/dmarc.rs @@ -4,10 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - +use crate::smtp::{ + DnsCache, TempDir, TestSMTP, + inbound::{TestMessage, TestReportingEvent, sign::SIGNATURES}, + session::{TestSession, VerifyResponse}, +}; use common::{Core, config::smtp::report::AggregateFrequency}; - use mail_auth::{ common::{parse::TxtRecordParser, verify::DomainKey}, dkim::DomainKeyReport, @@ -15,15 +17,8 @@ use mail_auth::{ report::DmarcResult, spf::Spf, }; -use store::Stores; -use utils::config::Config; - -use crate::smtp::{ - DnsCache, TempDir, TestSMTP, - inbound::{TestMessage, TestReportingEvent, sign::SIGNATURES}, - session::{TestSession, VerifyResponse}, -}; use smtp::core::Session; +use std::time::{Duration, Instant}; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/inbound/ehlo.rs b/tests/src/smtp/inbound/ehlo.rs index 572520d7..b332c9a4 100644 --- a/tests/src/smtp/inbound/ehlo.rs +++ b/tests/src/smtp/inbound/ehlo.rs @@ -4,18 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::Core; -use mail_auth::{SpfResult, common::parse::TxtRecordParser, spf::Spf}; - -use smtp::core::Session; -use utils::config::Config; - use crate::smtp::{ DnsCache, TestSMTP, session::{TestSession, VerifyResponse}, }; +use common::Core; +use mail_auth::{SpfResult, common::parse::TxtRecordParser, spf::Spf}; +use smtp::core::Session; +use std::time::{Duration, Instant}; const CONFIG: &str = r#" [session.data.limits] diff --git a/tests/src/smtp/inbound/limits.rs b/tests/src/smtp/inbound/limits.rs index 9012d4c6..01817026 100644 --- a/tests/src/smtp/inbound/limits.rs +++ b/tests/src/smtp/inbound/limits.rs @@ -4,18 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::Core; -use tokio::sync::watch; - -use smtp::core::Session; -use utils::config::Config; - use crate::smtp::{ TestSMTP, session::{TestSession, VerifyResponse}, }; +use common::Core; +use smtp::core::Session; +use std::time::{Duration, Instant}; +use tokio::sync::watch; const CONFIG: &str = r#" [session] diff --git a/tests/src/smtp/inbound/mail.rs b/tests/src/smtp/inbound/mail.rs index 08a24972..a8af5fc7 100644 --- a/tests/src/smtp/inbound/mail.rs +++ b/tests/src/smtp/inbound/mail.rs @@ -4,20 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant, SystemTime}; - -use common::Core; -use mail_auth::{IprevResult, SpfResult, common::parse::TxtRecordParser, spf::Spf}; -use smtp_proto::{MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_REQUIRETLS}; - -use smtp::core::Session; -use store::Stores; -use utils::config::Config; - use crate::smtp::{ DnsCache, TempDir, TestSMTP, session::{TestSession, VerifyResponse}, }; +use common::Core; +use mail_auth::{IprevResult, SpfResult, common::parse::TxtRecordParser, spf::Spf}; +use smtp::core::Session; +use smtp_proto::{MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_REQUIRETLS}; +use std::time::{Duration, Instant, SystemTime}; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/inbound/milter.rs b/tests/src/smtp/inbound/milter.rs index f364a4c7..7cb4b013 100644 --- a/tests/src/smtp/inbound/milter.rs +++ b/tests/src/smtp/inbound/milter.rs @@ -4,8 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{fs, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; - +use crate::smtp::{ + TempDir, TestSMTP, + inbound::TestMessage, + session::{TestSession, VerifyResponse, load_test_message}, +}; use ahash::AHashSet; use common::{ Core, @@ -13,7 +16,6 @@ use common::{ expr::if_block::IfBlock, manager::application::Resource, }; - use http_proto::{ToHttpResponse, request::fetch_body}; use hyper::{body, server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; @@ -30,19 +32,12 @@ use smtp::{ }, }, }; -use store::Stores; +use std::{fs, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::{TcpListener, TcpStream}, sync::watch, }; -use utils::config::Config; - -use crate::smtp::{ - TempDir, TestSMTP, - inbound::TestMessage, - session::{TestSession, VerifyResponse, load_test_message}, -}; #[derive(Debug, Deserialize)] struct HeaderTest { diff --git a/tests/src/smtp/inbound/mod.rs b/tests/src/smtp/inbound/mod.rs index 5d67206e..a62ed6a3 100644 --- a/tests/src/smtp/inbound/mod.rs +++ b/tests/src/smtp/inbound/mod.rs @@ -4,24 +4,19 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Duration; - +use super::{QueueReceiver, ReportReceiver}; use common::{ Server, ipc::{DmarcEvent, QueueEvent, QueueEventStatus, ReportingEvent, TlsEvent}, }; +use smtp::queue::{Message, MessageWrapper, QueueId, QueuedMessage}; +use std::time::Duration; use store::{ Deserialize, IterateParams, U64_LEN, ValueKey, - write::{ - AlignedBytes, Archive, QueueClass, ReportEvent, ValueClass, key::DeserializeBigEndian, - }, + write::{AlignedBytes, Archive, QueueClass, ValueClass, key::DeserializeBigEndian}, }; use tokio::sync::mpsc::error::TryRecvError; -use smtp::queue::{Message, MessageWrapper, QueueId, QueuedMessage}; - -use super::{QueueReceiver, ReportReceiver}; - pub mod antispam; pub mod asn; pub mod auth; diff --git a/tests/src/smtp/inbound/rcpt.rs b/tests/src/smtp/inbound/rcpt.rs index 23a25687..48d7312b 100644 --- a/tests/src/smtp/inbound/rcpt.rs +++ b/tests/src/smtp/inbound/rcpt.rs @@ -4,20 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Duration; - -use common::Core; - -use smtp_proto::{RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_SUCCESS}; -use store::Stores; -use utils::config::Config; - -use smtp::core::{Session, State}; - use crate::smtp::{ TempDir, TestSMTP, session::{TestSession, VerifyResponse}, }; +use common::Core; +use smtp::core::{Session, State}; +use smtp_proto::{RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_SUCCESS}; +use std::time::Duration; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/inbound/rewrite.rs b/tests/src/smtp/inbound/rewrite.rs index 28d19373..41439d0e 100644 --- a/tests/src/smtp/inbound/rewrite.rs +++ b/tests/src/smtp/inbound/rewrite.rs @@ -4,12 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::Core; - -use smtp::core::Session; -use utils::config::Config; - use crate::smtp::{TestSMTP, session::TestSession}; +use common::Core; +use smtp::core::Session; const CONFIG: &str = r#" [session.mail] diff --git a/tests/src/smtp/inbound/scripts.rs b/tests/src/smtp/inbound/scripts.rs index 1cd71456..f4d9adc9 100644 --- a/tests/src/smtp/inbound/scripts.rs +++ b/tests/src/smtp/inbound/scripts.rs @@ -4,9 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use core::panic; -use std::{fmt::Write, fs, path::PathBuf}; - use crate::{ AssertConfig, enable_logging, smtp::{ @@ -16,13 +13,12 @@ use crate::{ }, }; use common::Core; - +use core::panic; use smtp::{ core::Session, scripts::{ScriptResult, event_loop::RunScript}, }; -use store::Stores; -use utils::config::Config; +use std::{fmt::Write, fs, path::PathBuf}; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/inbound/sign.rs b/tests/src/smtp/inbound/sign.rs index 3e734dbc..46391162 100644 --- a/tests/src/smtp/inbound/sign.rs +++ b/tests/src/smtp/inbound/sign.rs @@ -4,23 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::Core; - -use mail_auth::{ - common::{parse::TxtRecordParser, verify::DomainKey}, - spf::Spf, -}; -use store::Stores; -use utils::config::Config; - use crate::smtp::{ DnsCache, TempDir, TestSMTP, inbound::TestMessage, session::{TestSession, VerifyResponse}, }; +use common::Core; +use mail_auth::{ + common::{parse::TxtRecordParser, verify::DomainKey}, + spf::Spf, +}; use smtp::core::Session; +use std::time::{Duration, Instant}; pub const SIGNATURES: &str = " [signature.rsa] diff --git a/tests/src/smtp/inbound/throttle.rs b/tests/src/smtp/inbound/throttle.rs index 07f3e686..ead693b4 100644 --- a/tests/src/smtp/inbound/throttle.rs +++ b/tests/src/smtp/inbound/throttle.rs @@ -4,13 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Duration; - use crate::smtp::{TempDir, TestSMTP, session::TestSession}; use common::Core; use smtp::core::{Session, SessionAddress}; -use store::Stores; -use utils::config::Config; +use std::time::Duration; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/inbound/vrfy.rs b/tests/src/smtp/inbound/vrfy.rs index 2a9edce9..52df5c08 100644 --- a/tests/src/smtp/inbound/vrfy.rs +++ b/tests/src/smtp/inbound/vrfy.rs @@ -4,13 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::Core; - -use store::Stores; -use utils::config::Config; - -use smtp::core::Session; - use crate::{ AssertConfig, smtp::{ @@ -18,6 +11,8 @@ use crate::{ session::{TestSession, VerifyResponse}, }, }; +use common::Core; +use smtp::core::Session; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/lookup/sql.rs b/tests/src/smtp/lookup/sql.rs index 09c71242..8850486d 100644 --- a/tests/src/smtp/lookup/sql.rs +++ b/tests/src/smtp/lookup/sql.rs @@ -4,21 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::{ - Core, - expr::{tokenizer::TokenMap, *}, -}; - -use directory::{ - QueryParams, Type, - backend::internal::{PrincipalField, PrincipalSet, PrincipalValue, manage::ManageDirectory}, -}; -use mail_auth::MX; -use store::Stores; -use utils::config::Config; - use crate::{ directory::DirectoryStore, smtp::{ @@ -26,7 +11,13 @@ use crate::{ session::{TestSession, VerifyResponse}, }, }; +use common::{ + Core, + expr::{tokenizer::TokenMap, *}, +}; +use mail_auth::MX; use smtp::{core::Session, queue::RecipientDomain}; +use std::time::{Duration, Instant}; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/lookup/utils.rs b/tests/src/smtp/lookup/utils.rs index 379a9ed0..094a64e1 100644 --- a/tests/src/smtp/lookup/utils.rs +++ b/tests/src/smtp/lookup/utils.rs @@ -4,8 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::net::IpAddr; - use crate::smtp::TestSMTP; use ::smtp::outbound::NextHop; use common::{ @@ -29,8 +27,8 @@ use smtp::{ }, reporting::AggregateTimestamp, }; +use std::net::IpAddr; use store::write::now; -use utils::config::Config; const CONFIG: &str = r#" [queue.connection.test.timeout] diff --git a/tests/src/smtp/management/queue.rs b/tests/src/smtp/management/queue.rs index 16eacd16..b4e465d4 100644 --- a/tests/src/smtp/management/queue.rs +++ b/tests/src/smtp/management/queue.rs @@ -4,21 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use ahash::{AHashMap, HashMap, HashSet}; -use common::config::server::ServerProtocol; - -use http::management::queue::Message; -use mail_auth::MX; -use mail_parser::DateTime; -use reqwest::{Method, StatusCode, header::AUTHORIZATION}; - use crate::{ jmap::ManagementApi, smtp::{DnsCache, TestSMTP, session::TestSession}, }; +use ahash::{AHashMap, HashMap, HashSet}; +use common::config::server::ServerProtocol; +use mail_auth::MX; +use mail_parser::DateTime; +use reqwest::{Method, StatusCode, header::AUTHORIZATION}; use smtp::queue::{QueueId, Status, manager::SpawnQueue}; +use std::time::{Duration, Instant}; const LOCAL: &str = r#" [storage] diff --git a/tests/src/smtp/management/report.rs b/tests/src/smtp/management/report.rs index c7ee3402..1d3a0575 100644 --- a/tests/src/smtp/management/report.rs +++ b/tests/src/smtp/management/report.rs @@ -4,15 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::sync::Arc; - +use crate::{ + jmap::ManagementApi, + smtp::{TestSMTP, management::queue::List}, +}; use ahash::{AHashMap, HashSet}; use common::{ config::{server::ServerProtocol, smtp::report::AggregateFrequency}, ipc::{DmarcEvent, PolicyType, TlsEvent}, }; - -use http::management::queue::Report; use mail_auth::{ common::parse::TxtRecordParser, dmarc::Dmarc, @@ -23,12 +23,8 @@ use mail_auth::{ }, }; use reqwest::Method; - -use crate::{ - jmap::ManagementApi, - smtp::{TestSMTP, management::queue::List}, -}; -use smtp::reporting::{SmtpReporting, scheduler::SpawnReport}; +use smtp::reporting::scheduler::SpawnReport; +use std::sync::Arc; const CONFIG: &str = r#" [storage] diff --git a/tests/src/smtp/mod.rs b/tests/src/smtp/mod.rs index c447b77d..bda84c54 100644 --- a/tests/src/smtp/mod.rs +++ b/tests/src/smtp/mod.rs @@ -4,32 +4,27 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - path::PathBuf, - sync::Arc, -}; - +use crate::{AssertConfig, store::cleanup::store_destroy}; use common::{ Core, Data, Inner, Server, config::{ server::{Listeners, ServerProtocol}, smtp::resolver::Tlsa, - spamfilter::IpResolver, }, ipc::{QueueEvent, ReportingEvent}, manager::boot::{IpcReceivers, build_ipc}, }; - use http::HttpSessionManager; use mail_auth::{MX, Txt, common::resolver::IntoFqdn}; use session::{DummyIo, TestSession}; use smtp::core::{Session, SmtpSessionManager}; -use store::{BlobStore, Store, Stores}; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + path::PathBuf, + sync::Arc, +}; +use store::{BlobStore, Store}; use tokio::sync::{mpsc, watch}; -use utils::config::Config; - -use crate::{AssertConfig, store::cleanup::store_destroy}; pub mod config; pub mod inbound; diff --git a/tests/src/smtp/queue/concurrent.rs b/tests/src/smtp/queue/concurrent.rs index 701d54f4..1b2daa65 100644 --- a/tests/src/smtp/queue/concurrent.rs +++ b/tests/src/smtp/queue/concurrent.rs @@ -4,16 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::{config::server::ServerProtocol, core::BuildServer, ipc::QueueEvent}; -use mail_auth::MX; - use crate::{ smtp::{DnsCache, TestSMTP, session::TestSession}, store::cleanup::store_assert_is_empty, }; +use common::{config::server::ServerProtocol, ipc::QueueEvent}; +use mail_auth::MX; use smtp::queue::manager::Queue; +use std::time::{Duration, Instant}; const LOCAL: &str = r#" [spam-filter] diff --git a/tests/src/smtp/queue/virtualq.rs b/tests/src/smtp/queue/virtualq.rs index c3ca66cf..9382f548 100644 --- a/tests/src/smtp/queue/virtualq.rs +++ b/tests/src/smtp/queue/virtualq.rs @@ -4,20 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::{Duration, Instant}; - -use common::{ - config::{server::ServerProtocol, smtp::queue::QueueName}, - core::BuildServer, - ipc::QueueEvent, -}; -use mail_auth::MX; - use crate::{ smtp::{DnsCache, TestSMTP, session::TestSession}, store::cleanup::store_assert_is_empty, }; +use common::{ + config::{server::ServerProtocol, smtp::queue::QueueName}, + ipc::QueueEvent, +}; +use mail_auth::MX; use smtp::queue::manager::Queue; +use std::time::{Duration, Instant}; const LOCAL: &str = r#" [spam-filter] diff --git a/tests/src/smtp/reporting/analyze.rs b/tests/src/smtp/reporting/analyze.rs index 3135e19a..4c976de7 100644 --- a/tests/src/smtp/reporting/analyze.rs +++ b/tests/src/smtp/reporting/analyze.rs @@ -4,14 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Duration; - use crate::smtp::{TestSMTP, inbound::TestQueueEvent, session::TestSession}; - -use store::{ - IterateParams, ValueKey, - write::{ReportClass, ValueClass}, -}; +use std::time::Duration; +use store::{IterateParams, ValueKey, write::ValueClass}; const CONFIG: &str = r#" [session.rcpt] diff --git a/tests/src/smtp/reporting/scheduler.rs b/tests/src/smtp/reporting/scheduler.rs index c13dc441..a5c9f1bd 100644 --- a/tests/src/smtp/reporting/scheduler.rs +++ b/tests/src/smtp/reporting/scheduler.rs @@ -18,10 +18,7 @@ use mail_auth::{ }; use store::write::QueueClass; -use smtp::reporting::{ - dmarc::{DmarcFormat, DmarcReporting}, - tls::TlsReporting, -}; +use smtp::reporting::{dmarc::DmarcReporting, tls::TlsReporting}; use crate::smtp::TestSMTP; diff --git a/tests/src/smtp/session.rs b/tests/src/smtp/session.rs index 5a769e01..8e3b801d 100644 --- a/tests/src/smtp/session.rs +++ b/tests/src/smtp/session.rs @@ -4,14 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{borrow::Cow, path::PathBuf, sync::Arc}; - use common::{ Server, config::server::ServerProtocol, - listener::{ServerInstance, SessionStream, TcpAcceptor, limiter::ConcurrencyLimiter}, + network::{ServerInstance, SessionStream, TcpAcceptor, limiter::ConcurrencyLimiter}, }; use rustls::{ServerConfig, server::ResolvesServerCert}; +use std::{borrow::Cow, path::PathBuf, sync::Arc}; use tokio::{ io::{AsyncRead, AsyncWrite}, sync::watch, diff --git a/tests/src/store/blob.rs b/tests/src/store/blob.rs index eac90d3e..8d812e85 100644 --- a/tests/src/store/blob.rs +++ b/tests/src/store/blob.rs @@ -8,14 +8,12 @@ use crate::store::{CONFIG, TempDir, cleanup::store_destroy}; use ahash::AHashMap; use common::{Core, Inner, Server, config::storage::Storage}; use email::message::metadata::MessageMetadata; -use http::management::stores::destroy_account_blobs; use std::sync::Arc; use store::{ - BlobStore, Serialize, SerializeInfallible, Stores, + BlobStore, Serialize, SerializeInfallible, write::{Archiver, BatchBuilder, BlobLink, BlobOp, ValueClass, blob::BlobQuota, now}, }; use types::{blob::BlobClass, blob_hash::BlobHash, collection::Collection, field::EmailField}; -use utils::config::Config; #[tokio::test] pub async fn blob_tests() { diff --git a/tests/src/store/import_export.rs b/tests/src/store/import_export.rs index 061fb8e7..491d41df 100644 --- a/tests/src/store/import_export.rs +++ b/tests/src/store/import_export.rs @@ -13,8 +13,8 @@ use common::{Core, DATABASE_SCHEMA_VERSION, manager::backup::BackupParams}; use store::{ rand, write::{ - AnyClass, AnyKey, BatchBuilder, BlobLink, BlobOp, DirectoryClass, Operation, QueueClass, - QueueEvent, ValueClass, + AnyClass, AnyKey, BatchBuilder, BlobLink, BlobOp, Operation, QueueClass, QueueEvent, + ValueClass, }, *, }; diff --git a/tests/src/store/lookup.rs b/tests/src/store/lookup.rs index 5f226bc1..0a17e714 100644 --- a/tests/src/store/lookup.rs +++ b/tests/src/store/lookup.rs @@ -4,11 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::time::Duration; - -use store::{InMemoryStore, Stores, dispatch::lookup::KeyValue}; -use utils::config::{Config, Rate}; - use crate::{ AssertConfig, store::{ @@ -16,6 +11,8 @@ use crate::{ cleanup::{store_assert_is_empty, store_destroy}, }, }; +use std::time::Duration; +use store::{InMemoryStore, dispatch::lookup::KeyValue}; #[tokio::test] pub async fn lookup_tests() { diff --git a/tests/src/store/mod.rs b/tests/src/store/mod.rs index 6e9b186f..9e632287 100644 --- a/tests/src/store/mod.rs +++ b/tests/src/store/mod.rs @@ -16,8 +16,6 @@ use crate::{ store::cleanup::{search_store_destroy, store_destroy}, }; use std::io::Read; -use store::Stores; -use utils::config::Config; pub struct TempDir { pub path: std::path::PathBuf, diff --git a/tests/src/store/ops.rs b/tests/src/store/ops.rs index 7a8279cd..78bce056 100644 --- a/tests/src/store/ops.rs +++ b/tests/src/store/ops.rs @@ -4,20 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::store::cleanup::store_assert_is_empty; use ahash::AHashSet; use std::collections::HashSet; use store::{ Store, ValueKey, rand::{self, Rng}, - write::{ - AlignedBytes, Archive, Archiver, BatchBuilder, DirectoryClass, MergeResult, Params, - ValueClass, - }, + write::{AlignedBytes, Archive, Archiver, BatchBuilder, MergeResult, Params, ValueClass}, }; use types::collection::{Collection, SyncCollection}; -use crate::store::cleanup::store_assert_is_empty; - // FDB max value const MAX_VALUE_SIZE: usize = 100000; diff --git a/tests/src/system/mod.rs b/tests/src/system/mod.rs new file mode 100644 index 00000000..69b01f91 --- /dev/null +++ b/tests/src/system/mod.rs @@ -0,0 +1,5 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ diff --git a/tests/src/utils/account.rs b/tests/src/utils/account.rs new file mode 100644 index 00000000..c2bd7e76 --- /dev/null +++ b/tests/src/utils/account.rs @@ -0,0 +1,438 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::utils::jmap::JmapResponse; +use base64::{Engine, engine::general_purpose}; +use hyper::header; +use jmap_client::client::{Client, Credentials}; +use serde_json::{Value, json}; +use std::{fmt::Display, time::Duration}; +use types::id::Id; + +pub struct Account { + name: &'static str, + secret: &'static str, + emails: &'static [&'static str], + id: Id, + id_string: String, + client: Client, +} + +impl Account { + pub fn id(&self) -> &Id { + &self.id + } + + pub fn id_string(&self) -> &str { + &self.id_string + } + + pub fn client(&self) -> &Client { + &self.client + } + + pub fn name(&self) -> &'static str { + self.name + } + pub fn secret(&self) -> &'static str { + self.secret + } + + pub fn emails(&self) -> &'static [&'static str] { + self.emails + } + + pub async fn client_owned(&self) -> Client { + Client::new() + .credentials(Credentials::basic(self.name(), self.secret())) + .timeout(Duration::from_secs(3600)) + .accept_invalid_certs(true) + .follow_redirects(["127.0.0.1"]) + .connect("https://127.0.0.1:8899") + .await + .unwrap() + } + + pub async fn jmap_get( + &self, + object: impl Display, + properties: impl IntoIterator, + ids: impl IntoIterator, + ) -> JmapResponse { + self.jmap_get_account(self, object, properties, ids).await + } + + pub async fn jmap_get_account( + &self, + account: &Account, + object: impl Display, + properties: impl IntoIterator, + ids: impl IntoIterator, + ) -> JmapResponse { + let ids = ids + .into_iter() + .map(|id| Value::String(id.to_string())) + .collect::>(); + self.jmap_method_calls(json!([[ + format!("{object}/get"), + { + "accountId": account.id_string(), + "properties": properties + .into_iter() + .map(|p| Value::String(p.to_string())) + .collect::>(), + "ids": if !ids.is_empty() { Some(ids) } else { None } + }, + "0" + ]])) + .await + } + + pub async fn jmap_query( + &self, + object: impl Display, + filter: impl IntoIterator)>, + sort_by: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + let filter = filter + .into_iter() + .map(|(k, v)| (k.to_string(), v.into())) + .collect::>(); + let sort_by = sort_by + .into_iter() + .map(|id| { + json! ({ + "property": id.to_string() + }) + }) + .collect::>(); + let arguments = [ + ("filter".to_string(), Value::Object(filter)), + ("sort".to_string(), Value::Array(sort_by)), + ] + .into_iter() + .chain( + arguments + .into_iter() + .map(|(k, v)| (k.to_string(), v.into())), + ) + .collect::>(); + + self.jmap_method_calls(json!([[format!("{object}/query"), arguments, "0"]])) + .await + } + + pub async fn jmap_create( + &self, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + self.jmap_create_account(self, object, items, arguments) + .await + } + + pub async fn jmap_create_account( + &self, + account: &Account, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + let create = items + .into_iter() + .enumerate() + .map(|(i, item)| (format!("i{i}"), item)) + .collect::>(); + let arguments = [ + ( + "accountId".to_string(), + Value::String(account.id_string().to_string()), + ), + ("create".to_string(), Value::Object(create)), + ] + .into_iter() + .chain( + arguments + .into_iter() + .map(|(k, v)| (k.to_string(), v.into())), + ) + .collect::>(); + + self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) + .await + } + + pub async fn jmap_update( + &self, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + self.jmap_update_account(self, object, items, arguments) + .await + } + + pub async fn jmap_update_account( + &self, + account: &Account, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + let update = items + .into_iter() + .map(|(i, item)| (i.to_string(), item)) + .collect::>(); + let arguments = [ + ( + "accountId".to_string(), + Value::String(account.id_string().to_string()), + ), + ("update".to_string(), Value::Object(update)), + ] + .into_iter() + .chain( + arguments + .into_iter() + .map(|(k, v)| (k.to_string(), v.into())), + ) + .collect::>(); + + self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) + .await + } + + pub async fn jmap_destroy( + &self, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + self.jmap_destroy_account(self, object, items, arguments) + .await + } + + pub async fn jmap_destroy_account( + &self, + account: &Account, + object: impl Display, + items: impl IntoIterator, + arguments: impl IntoIterator)>, + ) -> JmapResponse { + let destroy = items + .into_iter() + .map(|id| Value::String(id.to_string())) + .collect::>(); + let arguments = [ + ( + "accountId".to_string(), + Value::String(account.id_string().to_string()), + ), + ("destroy".to_string(), Value::Array(destroy)), + ] + .into_iter() + .chain( + arguments + .into_iter() + .map(|(k, v)| (k.to_string(), v.into())), + ) + .collect::>(); + + self.jmap_method_calls(json!([[format!("{object}/set"), arguments, "0"]])) + .await + } + + pub async fn jmap_copy( + &self, + from_account: &Account, + to_account: &Account, + object: impl Display, + items: impl IntoIterator, + on_success_destroy: bool, + ) -> JmapResponse { + self.jmap_method_calls(json!([[ + format!("{object}/copy"), + { + "fromAccountId": from_account.id_string(), + "accountId": to_account.id_string(), + "onSuccessDestroyOriginal": on_success_destroy, + "create": items + .into_iter() + .map(|(i, item)| (i.to_string(), item)).collect::>() + }, + "0" + ]])) + .await + } + + pub async fn jmap_changes(&self, object: impl Display, state: impl Display) -> JmapResponse { + self.jmap_method_calls(json!([[ + format!("{object}/changes"), + { + "sinceState": state.to_string() + }, + "0" + ]])) + .await + } + + pub async fn jmap_method_call(&self, method_name: &str, body: Value) -> JmapResponse { + self.jmap_method_calls(json!([[method_name, body, "0"]])) + .await + } + + pub async fn jmap_method_calls(&self, calls: Value) -> JmapResponse { + let mut headers = header::HeaderMap::new(); + + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_str(&format!( + "Basic {}", + general_purpose::STANDARD.encode(format!("{}:{}", self.name(), self.secret())) + )) + .unwrap(), + ); + + let body = json!({ + "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:quota" ], + "methodCalls": calls + }); + + JmapResponse( + serde_json::from_slice( + &reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(1000)) + .default_headers(headers) + .build() + .unwrap() + .post("https://127.0.0.1:8899/jmap") + .body(body.to_string()) + .send() + .await + .unwrap() + .bytes() + .await + .unwrap(), + ) + .unwrap(), + ) + } + + pub async fn jmap_session_object(&self) -> JmapResponse { + let mut headers = header::HeaderMap::new(); + + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_str(&format!( + "Basic {}", + general_purpose::STANDARD.encode(format!("{}:{}", self.name(), self.secret())) + )) + .unwrap(), + ); + + JmapResponse( + serde_json::from_slice( + &reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(1000)) + .default_headers(headers) + .build() + .unwrap() + .get("https://127.0.0.1:8899/jmap/session") + .send() + .await + .unwrap() + .bytes() + .await + .unwrap(), + ) + .unwrap(), + ) + } + + pub async fn destroy_all_addressbooks(&self) { + self.jmap_method_calls(json!([[ + "AddressBook/get", + { + "ids" : (), + "properties" : [ + "id" + ] + }, + "R1" + ], + [ + "AddressBook/set", + { + "#destroy" : { + "resultOf": "R1", + "name": "AddressBook/get", + "path": "/list/*/id" + }, + "onDestroyRemoveContents" : true + }, + "R2" + ] + ])) + .await; + } + + pub async fn destroy_all_calendars(&self) { + self.jmap_method_calls(json!([[ + "Calendar/get", + { + "ids" : (), + "properties" : [ + "id" + ] + }, + "R1" + ], + [ + "Calendar/set", + { + "#destroy" : { + "resultOf": "R1", + "name": "Calendar/get", + "path": "/list/*/id" + }, + "onDestroyRemoveEvents" : true + }, + "R2" + ] + ])) + .await; + } + + pub async fn destroy_all_event_notifications(&self) { + self.jmap_method_calls(json!([[ + "CalendarEventNotification/get", + { + "ids" : (), + "properties" : [ + "id" + ] + }, + "R1" + ], + [ + "CalendarEventNotification/set", + { + "#destroy" : { + "resultOf": "R1", + "name": "CalendarEventNotification/get", + "path": "/list/*/id" + } + }, + "R2" + ] + ])) + .await; + } +} diff --git a/tests/src/store/cleanup.rs b/tests/src/utils/cleanup.rs similarity index 97% rename from tests/src/store/cleanup.rs rename to tests/src/utils/cleanup.rs index ed91572d..9e8972b0 100644 --- a/tests/src/store/cleanup.rs +++ b/tests/src/utils/cleanup.rs @@ -70,7 +70,7 @@ pub async fn search_store_destroy(store: &SearchStore) { if let Err(err) = store.drop_indexes().await { eprintln!("Failed to drop elasticsearch indexes: {}", err); } - store.create_indexes(3, 0, false).await.unwrap(); + store.create_indexes().await.unwrap(); } SearchStore::MeiliSearch(store) => { if let Err(err) = store.drop_indexes().await { @@ -130,7 +130,7 @@ pub async fn store_blob_expire_all(store: &Store) { store .iterate( IterateParams::new(from_key, to_key).ascending(), - |key, value| { + |key, _| { if key.len() == BLOB_HASH_LEN + U32_LEN + U64_LEN { let account_id = key .deserialize_be_u32(BLOB_HASH_LEN) @@ -223,7 +223,12 @@ pub async fn store_lookup_expire_all(store: &Store) { pub async fn store_assert_is_empty(store: &Store, blob_store: BlobStore, include_directory: bool) { store_blob_expire_all(store).await; store_lookup_expire_all(store).await; - store.purge_blobs(blob_store).await.unwrap(); + for shard_idx in 0..=u8::MAX { + store + .purge_blobs(blob_store.clone(), shard_idx) + .await + .unwrap(); + } store.purge_store().await.unwrap(); let store = store.clone(); diff --git a/tests/src/http_server.rs b/tests/src/utils/http_server.rs similarity index 95% rename from tests/src/http_server.rs rename to tests/src/utils/http_server.rs index 62377059..29136034 100644 --- a/tests/src/http_server.rs +++ b/tests/src/utils/http_server.rs @@ -4,17 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::sync::Arc; - +use crate::{AssertConfig, add_test_certs}; use ahash::AHashMap; -use common::{Caches, Core, Data, Inner, config::server::Listeners, listener::SessionData}; +use common::{Caches, Core, Data, Inner, config::server::Listeners, network::SessionData}; use http_proto::{HttpResponse, request::fetch_body}; use hyper::{Method, Uri, body, server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; +use std::sync::Arc; use tokio::sync::watch; -use utils::config::Config; - -use crate::{AssertConfig, add_test_certs}; const MOCK_HTTP_SERVER: &str = r#" [server] @@ -94,9 +91,9 @@ pub async fn spawn_mock_http_server( }) } -impl common::listener::SessionManager for HttpSessionManager { +impl common::network::SessionManager for HttpSessionManager { #[allow(clippy::manual_async_fn)] - fn handle( + fn handle( self, session: SessionData, ) -> impl std::future::Future + Send { diff --git a/tests/src/utils/jmap.rs b/tests/src/utils/jmap.rs new file mode 100644 index 00000000..d53c4bc2 --- /dev/null +++ b/tests/src/utils/jmap.rs @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use serde_json::Value; +use std::fmt::Display; + +pub struct JmapResponse(pub Value); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ChangeType<'x> { + Created(&'x str), + Updated(&'x str), + Destroyed(&'x str), +} + +impl JmapResponse { + pub fn created(&self, item_idx: u32) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/created/i{item_idx}")) + .unwrap_or_else(|| panic!("Missing created item {item_idx}: {self:?}")) + } + + pub fn not_created(&self, item_idx: u32) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/notCreated/i{item_idx}")) + .unwrap_or_else(|| panic!("Missing not created item {item_idx}: {self:?}")) + } + + pub fn updated(&self, id: &str) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/updated/{id}")) + .unwrap_or_else(|| panic!("Missing updated item {id}: {self:?}")) + } + + pub fn not_updated(&self, id: &str) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/notUpdated/{id}")) + .unwrap_or_else(|| panic!("Missing not updated item {id}: {self:?}")) + } + + pub fn copied(&self, id: &str) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/created/{id}")) + .unwrap_or_else(|| panic!("Missing created item {id}: {self:?}")) + } + + pub fn method_response(&self) -> &Value { + self.0 + .pointer("/methodResponses/0/1") + .unwrap_or_else(|| panic!("Missing method response in response: {self:?}")) + } + + pub fn list_array(&self) -> &Value { + self.0 + .pointer("/methodResponses/0/1/list") + .unwrap_or_else(|| panic!("Missing list in response: {self:?}")) + } + + pub fn list(&self) -> &[Value] { + self.0 + .pointer("/methodResponses/0/1/list") + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("Missing list in response: {self:?}")) + } + + pub fn not_found(&self) -> impl Iterator { + self.0 + .pointer("/methodResponses/0/1/notFound") + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("Missing notFound in response: {self:?}")) + .iter() + .map(|v| v.as_str().unwrap()) + } + + pub fn ids(&self) -> impl Iterator { + self.0 + .pointer("/methodResponses/0/1/ids") + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("Missing ids in response: {self:?}")) + .iter() + .map(|v| v.as_str().unwrap()) + } + + pub fn destroyed(&self) -> impl Iterator { + self.0 + .pointer("/methodResponses/0/1/destroyed") + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("Missing destroyed in response: {self:?}")) + .iter() + .map(|v| v.as_str().unwrap()) + } + + pub fn not_destroyed(&self, id: &str) -> &Value { + self.0 + .pointer(&format!("/methodResponses/0/1/notDestroyed/{id}")) + .unwrap_or_else(|| panic!("Missing not destroyed item {id}: {self:?}")) + } + + pub fn state(&self) -> &str { + self.0 + .pointer("/methodResponses/0/1/state") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("Missing state in response: {self:?}")) + } + + pub fn new_state(&self) -> &str { + self.0 + .pointer("/methodResponses/0/1/newState") + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("Missing new state in response: {self:?}")) + } + + pub fn changes(&self) -> impl Iterator> { + self.changes_by_type("created") + .map(ChangeType::Created) + .chain(self.changes_by_type("updated").map(ChangeType::Updated)) + .chain(self.changes_by_type("destroyed").map(ChangeType::Destroyed)) + } + + fn changes_by_type(&self, typ: &str) -> impl Iterator { + self.0 + .pointer(&format!("/methodResponses/0/1/{typ}")) + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("Missing {typ} changes in response: {self:?}")) + .iter() + .map(|v| v.as_str().unwrap()) + } + + pub fn pointer(&self, pointer: &str) -> Option<&Value> { + self.0.pointer(pointer) + } + + pub fn into_inner(self) -> Value { + self.0 + } +} + +pub trait JmapUtils { + fn id(&self) -> &str { + self.text_field("id") + } + + fn blob_id(&self) -> &str { + self.text_field("blobId") + } + + fn typ(&self) -> &str { + self.text_field("type") + } + + fn description(&self) -> &str { + self.text_field("description") + } + + fn with_property(self, field: impl Display, value: impl Into) -> Self; + + fn text_field(&self, field: &str) -> &str; + + fn assert_is_equal(&self, other: Value); +} + +impl JmapUtils for Value { + fn text_field(&self, field: &str) -> &str { + self.pointer(&format!("/{field}")) + .and_then(|v| v.as_str()) + .unwrap_or_else(|| panic!("Missing {field} in object: {self:?}")) + } + fn assert_is_equal(&self, expected: Value) { + if self != &expected { + panic!( + "Values are not equal:\ngot: {}\nexpected: {}", + serde_json::to_string_pretty(self).unwrap(), + serde_json::to_string_pretty(&expected).unwrap() + ); + } + } + fn with_property(mut self, field: impl Display, value: impl Into) -> Self { + if let Value::Object(map) = &mut self { + map.insert(field.to_string(), value.into()); + } else { + panic!("Not an object: {self:?}"); + } + self + } +} + +impl<'x> ChangeType<'x> { + pub fn as_created(&self) -> &str { + match self { + ChangeType::Created(id) => id, + _ => panic!("Not a created change: {self:?}"), + } + } + + pub fn as_updated(&self) -> &str { + match self { + ChangeType::Updated(id) => id, + _ => panic!("Not an updated change: {self:?}"), + } + } + + pub fn as_destroyed(&self) -> &str { + match self { + ChangeType::Destroyed(id) => id, + _ => panic!("Not a destroyed change: {self:?}"), + } + } +} + +impl Display for JmapResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +impl std::fmt::Debug for JmapResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + serde_json::to_string_pretty(&self.0) + .map_err(|_| std::fmt::Error) + .and_then(|s| std::fmt::Display::fmt(&s, f)) + } +} + +pub trait IntoJmapSet { + fn into_jmap_set(self) -> Value; +} + +impl> IntoJmapSet for T { + fn into_jmap_set(self) -> Value { + Value::Object( + self.into_iter() + .map(|id| (id.to_string(), Value::Bool(true))) + .collect::>(), + ) + } +} diff --git a/tests/src/utils/mod.rs b/tests/src/utils/mod.rs new file mode 100644 index 00000000..d0ccfc7b --- /dev/null +++ b/tests/src/utils/mod.rs @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +pub mod account; +pub mod cleanup; +pub mod jmap; +pub mod storage; diff --git a/tests/src/utils/storage.rs b/tests/src/utils/storage.rs new file mode 100644 index 00000000..25e651dc --- /dev/null +++ b/tests/src/utils/storage.rs @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::utils::cleanup::{search_store_destroy, store_assert_is_empty}; +use common::Server; +use registry::schema::structs::Task; +use std::time::Duration; +use store::{ + Deserialize, IterateParams, ValueKey, + write::{TaskQueueClass, ValueClass}, +}; + +pub async fn wait_for_index(server: &Server) { + let mut count = 0; + loop { + let mut has_index_tasks = None; + server + .core + .storage + .data + .iterate( + IterateParams::new( + ValueKey::from(ValueClass::TaskQueue(TaskQueueClass::Task { id: 0 })), + ValueKey::from(ValueClass::TaskQueue(TaskQueueClass::Task { id: u64::MAX })), + ) + .ascending(), + |_, value| { + has_index_tasks = Some(Task::deserialize(value)?); + + Ok(false) + }, + ) + .await + .unwrap(); + + if let Some(task) = has_index_tasks { + count += 1; + if count % 10 == 0 { + println!("Waiting for pending task {:?}...", task); + } + tokio::time::sleep(Duration::from_millis(300)).await; + } else { + break; + } + } +} + +pub async fn assert_is_empty(server: &Server) { + // Wait for pending index tasks + wait_for_index(server).await; + + // Assert is empty + store_assert_is_empty(server.store(), server.core.storage.blob.clone(), false).await; + search_store_destroy(server.search_store()).await; + + // Clean caches + for cache in [ + &server.inner.cache.events, + &server.inner.cache.contacts, + &server.inner.cache.files, + &server.inner.cache.scheduling, + ] { + cache.clear(); + } + server.inner.cache.messages.clear(); +} diff --git a/tests/src/webdav/cal_scheduling.rs b/tests/src/webdav/cal_scheduling.rs index b3901a75..d1ca6d67 100644 --- a/tests/src/webdav/cal_scheduling.rs +++ b/tests/src/webdav/cal_scheduling.rs @@ -21,9 +21,7 @@ use dav_proto::schema::property::{CalDavProperty, DavProperty, WebDavProperty}; use email::cache::MessageCacheFetch; use groupware::{ cache::GroupwareCache, - scheduling::{ - ArchivedItipSummary, ItipField, ItipParticipant, ItipSummary, ItipTime, ItipValue, - }, + scheduling::{ItipField, ItipParticipant, ItipSummary, ItipTime, ItipValue}, }; use hyper::StatusCode; use mail_parser::{DateTime, MessageParser}; diff --git a/tests/src/webdav/mod.rs b/tests/src/webdav/mod.rs index 279601e4..c3951cc2 100644 --- a/tests/src/webdav/mod.rs +++ b/tests/src/webdav/mod.rs @@ -14,7 +14,6 @@ use crate::{ }, }; use ::managesieve::core::ManageSieveSessionManager; -use ::store::Stores; use ahash::{AHashMap, AHashSet}; use base64::{Engine, engine::general_purpose::STANDARD}; use common::{ @@ -23,14 +22,12 @@ use common::{ server::{Listeners, ServerProtocol}, telemetry::Telemetry, }, - core::BuildServer, manager::boot::build_ipc, }; use dav_proto::{ schema::property::{DavProperty, WebDavProperty}, xml_pretty_print, }; -use directory::Permission; use email::message::metadata::MessageMetadata; use groupware::{DavResourceName, cache::GroupwareCache}; use http::HttpSessionManager;