Registry testing - part 12
This commit is contained in:
@@ -26,8 +26,10 @@ pub struct Account {
|
||||
name: &'static str,
|
||||
secret: &'static str,
|
||||
emails: &'static [&'static str],
|
||||
description: &'static str,
|
||||
id: Id,
|
||||
id_string: String,
|
||||
pub http_listener_port: u16,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@@ -37,12 +39,29 @@ impl TestServer {
|
||||
name: &'static str,
|
||||
secret: &'static str,
|
||||
aliases: &'static [&'static str],
|
||||
description: &'static str,
|
||||
) -> Account {
|
||||
self.account(using_account)
|
||||
.create_user_account(name, secret, None, aliases, vec![])
|
||||
.create_user_account(name, secret, description, aliases, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_admin_account(&self, name: &'static str) -> Account {
|
||||
let admin = self
|
||||
.create_user_account(
|
||||
"admin",
|
||||
name,
|
||||
"these_pretzels_are_making_me_thirsty",
|
||||
&[],
|
||||
"Admin",
|
||||
)
|
||||
.await;
|
||||
self.account("admin")
|
||||
.assign_roles_to_account(admin.id(), &["user", "system"])
|
||||
.await;
|
||||
admin
|
||||
}
|
||||
|
||||
pub fn insert_account(&mut self, account: Account) {
|
||||
self.accounts.insert(account.name(), account);
|
||||
}
|
||||
@@ -53,14 +72,17 @@ impl Account {
|
||||
name: &'static str,
|
||||
secret: &'static str,
|
||||
emails: &'static [&'static str],
|
||||
description: &'static str,
|
||||
id: Id,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
secret,
|
||||
emails,
|
||||
description,
|
||||
id,
|
||||
id_string: id.to_string(),
|
||||
http_listener_port: 8899,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +101,11 @@ impl Account {
|
||||
pub fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &'static str {
|
||||
self.description
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &'static str {
|
||||
self.secret
|
||||
}
|
||||
@@ -107,7 +134,7 @@ impl Account {
|
||||
&self,
|
||||
name: &'static str,
|
||||
secret: &'static str,
|
||||
description: Option<&'static str>,
|
||||
description: &'static str,
|
||||
aliases: &'static [&'static str],
|
||||
extra_permissions: Vec<Permission>,
|
||||
) -> Account {
|
||||
@@ -128,7 +155,7 @@ impl Account {
|
||||
.rsplit_once('@')
|
||||
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
||||
.unwrap();
|
||||
let account_aliases = aliases.iter().map(|email| {
|
||||
let account_aliases = aliases.iter().filter(|email| **email != name).map(|email| {
|
||||
let (name, domain_id) = email
|
||||
.rsplit_once('@')
|
||||
.map(|(name, domain)| (name.to_string(), *domains.get(domain).unwrap()))
|
||||
@@ -150,7 +177,7 @@ impl Account {
|
||||
..Default::default()
|
||||
})]),
|
||||
aliases: List::from_iter(account_aliases),
|
||||
description: description.map(|d| d.to_string()),
|
||||
description: description.to_string().into(),
|
||||
permissions: Permissions::Merge(PermissionsList {
|
||||
disabled_permissions: Default::default(),
|
||||
enabled_permissions: Map::new(extra_permissions),
|
||||
@@ -159,13 +186,15 @@ impl Account {
|
||||
}))
|
||||
.await;
|
||||
|
||||
Account::new(name, secret, aliases, account_id)
|
||||
let mut account = Account::new(name, secret, aliases, description, account_id);
|
||||
account.http_listener_port = self.http_listener_port;
|
||||
account
|
||||
}
|
||||
|
||||
pub async fn create_group_account(
|
||||
&self,
|
||||
name: &'static str,
|
||||
description: Option<&'static str>,
|
||||
description: &'static str,
|
||||
aliases: &'static [&'static str],
|
||||
) -> Account {
|
||||
let mut domains = AHashMap::from_iter(
|
||||
@@ -203,12 +232,12 @@ impl Account {
|
||||
name: account_name,
|
||||
domain_id,
|
||||
aliases: List::from_iter(account_aliases),
|
||||
description: description.map(|d| d.to_string()),
|
||||
description: description.to_string().into(),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
Account::new(name, "", aliases, account_id)
|
||||
Account::new(name, "", aliases, description, account_id)
|
||||
}
|
||||
|
||||
pub async fn create_domain(&self, name: &'static str) -> Id {
|
||||
@@ -269,7 +298,7 @@ impl Account {
|
||||
.timeout(Duration::from_secs(3600))
|
||||
.accept_invalid_certs(true)
|
||||
.follow_redirects(["127.0.0.1"])
|
||||
.connect("https://127.0.0.1:8899")
|
||||
.connect(&format!("https://127.0.0.1:{}", self.http_listener_port))
|
||||
.await
|
||||
.unwrap();
|
||||
client.set_default_account_id(self.id_string());
|
||||
|
||||
@@ -155,6 +155,8 @@ pub trait AssertResult: Sized {
|
||||
|
||||
fn assert_response_code(self, code: &str) -> Self;
|
||||
fn assert_contains(self, text: &str) -> Self;
|
||||
fn assert_contains_any(self, expected_texts: &[&str]) -> Self;
|
||||
fn assert_not_contains(self, expected_text: &str) -> Self;
|
||||
fn assert_count(self, text: &str, occurrences: usize) -> Self;
|
||||
fn assert_equals(self, text: &str) -> Self;
|
||||
fn into_response_code(self) -> String;
|
||||
@@ -208,13 +210,39 @@ impl AssertResult for Vec<String> {
|
||||
self
|
||||
}
|
||||
|
||||
fn assert_contains(self, text: &str) -> Self {
|
||||
for line in &self {
|
||||
if line.contains(text) {
|
||||
return self;
|
||||
}
|
||||
fn assert_contains(self, expected_text: &str) -> Self {
|
||||
if self.iter().any(|line| line.contains(expected_text)) {
|
||||
self
|
||||
} else {
|
||||
panic!("Expected {:?} but got {}.", expected_text, self.join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_contains_any(self, expected_texts: &[&str]) -> Self {
|
||||
if self
|
||||
.iter()
|
||||
.any(|line| expected_texts.iter().any(|text| line.contains(text)))
|
||||
{
|
||||
self
|
||||
} else {
|
||||
panic!(
|
||||
"Expected any of {:?} but got {}.",
|
||||
expected_texts,
|
||||
self.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_not_contains(self, expected_text: &str) -> Self {
|
||||
if !self.iter().any(|line| line.contains(expected_text)) {
|
||||
self
|
||||
} else {
|
||||
panic!(
|
||||
"Not expecting {:?} but got it {}.",
|
||||
expected_text,
|
||||
self.join("\n")
|
||||
);
|
||||
}
|
||||
panic!("Expected response to contain {:?}, got {:?}", text, self);
|
||||
}
|
||||
|
||||
fn assert_count(self, text: &str, occurrences: usize) -> Self {
|
||||
|
||||
@@ -279,7 +279,10 @@ impl Account {
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap()
|
||||
.post("https://127.0.0.1:8899/jmap")
|
||||
.post(format!(
|
||||
"https://127.0.0.1:{}/jmap",
|
||||
self.http_listener_port
|
||||
))
|
||||
.body(body.to_string())
|
||||
.send()
|
||||
.await
|
||||
@@ -312,7 +315,10 @@ impl Account {
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap()
|
||||
.get("https://127.0.0.1:8899/jmap/session")
|
||||
.get(format!(
|
||||
"https://127.0.0.1:{}/jmap/session",
|
||||
self.http_listener_port
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
|
||||
@@ -14,6 +14,8 @@ pub mod jmap;
|
||||
pub mod pop3;
|
||||
pub mod registry;
|
||||
pub mod server;
|
||||
pub mod sieve;
|
||||
pub mod smtp;
|
||||
pub mod storage;
|
||||
pub mod temp_dir;
|
||||
pub mod webdav;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use mail_send::smtp::tls::build_tls_connector;
|
||||
use rustls_pki_types::ServerName;
|
||||
use std::time::Duration;
|
||||
@@ -36,19 +37,20 @@ impl Pop3Connection {
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
Pop3Connection {
|
||||
|
||||
let mut conn = Pop3Connection {
|
||||
reader: BufReader::new(reader).lines(),
|
||||
writer,
|
||||
}
|
||||
};
|
||||
|
||||
conn.assert_read(ResponseType::Ok).await;
|
||||
conn
|
||||
}
|
||||
|
||||
pub async fn connect_and_login() -> Self {
|
||||
let mut pop3 = Self::connect().await;
|
||||
pop3.assert_read(ResponseType::Ok).await;
|
||||
pop3.send("AUTH PLAIN AHBvcHBlckBleGFtcGxlLmNvbQBzZWNyZXQ=")
|
||||
.await;
|
||||
pop3.assert_read(ResponseType::Ok).await;
|
||||
pop3
|
||||
pub async fn authenticate(&mut self, user: &str, pass: &str) {
|
||||
let creds = general_purpose::STANDARD.encode(format!("\0{user}\0{pass}"));
|
||||
self.send(&format!("AUTH PLAIN {creds}")).await;
|
||||
self.assert_read(ResponseType::Ok).await;
|
||||
}
|
||||
|
||||
pub async fn assert_read(&mut self, rt: ResponseType) -> Vec<String> {
|
||||
|
||||
@@ -64,6 +64,28 @@ impl Account {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn registry_get_all<T: ObjectImpl>(&self) -> Vec<(Id, T)> {
|
||||
let name = T::OBJECT.as_str();
|
||||
|
||||
let response = self
|
||||
.jmap_get_account(
|
||||
self,
|
||||
format!("x:{name}"),
|
||||
Vec::<&str>::new(),
|
||||
Vec::<Id>::new(),
|
||||
)
|
||||
.await;
|
||||
let mut items = Vec::with_capacity(response.list().len());
|
||||
for item in response.list() {
|
||||
let id = item.object_id();
|
||||
let item = serde_json::from_str(&item.to_string()).unwrap_or_else(|_| {
|
||||
panic!("Failed to deserialize {item}");
|
||||
});
|
||||
items.push((id, item));
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
pub async fn registry_get_many(
|
||||
&self,
|
||||
object_type: ObjectType,
|
||||
|
||||
@@ -6,25 +6,32 @@
|
||||
|
||||
use crate::{
|
||||
AssertConfig,
|
||||
store::TempDir,
|
||||
smtp::session::{DummyIo, TestSession},
|
||||
utils::{
|
||||
account::Account,
|
||||
cleanup::{search_store_destroy, store_blob_expire_all, store_destroy},
|
||||
registry::UnwrapRegistryId,
|
||||
storage::{RegistryEnvStores, assert_is_empty, build_data_store, wait_for_tasks},
|
||||
temp_dir::TempDir,
|
||||
},
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use common::{
|
||||
BuildServer, Caches, Core, Data, Inner, Server,
|
||||
BuildServer, Caches, Core, Data, DavResources, Inner, Server,
|
||||
auth::FALLBACK_ADMIN_ID,
|
||||
config::{
|
||||
server::{Listeners, ServerProtocol},
|
||||
storage::Storage,
|
||||
telemetry::Telemetry,
|
||||
},
|
||||
manager::{boot::build_ipc, defaults::BootstrapDefaults},
|
||||
ipc::{QueueEvent, ReportingEvent},
|
||||
manager::{
|
||||
boot::{IpcReceivers, build_ipc},
|
||||
defaults::BootstrapDefaults,
|
||||
},
|
||||
};
|
||||
use email::message::metadata::MessageMetadata;
|
||||
use groupware::cache::GroupwareCache;
|
||||
use http::HttpSessionManager;
|
||||
use imap::core::ImapSessionManager;
|
||||
use jmap_client::client::Client;
|
||||
@@ -41,25 +48,29 @@ use registry::{
|
||||
use services::{SpawnServices, broadcast::subscriber::spawn_broadcast_subscriber};
|
||||
use smtp::{
|
||||
SpawnQueueManager,
|
||||
core::SmtpSessionManager,
|
||||
core::{Session, SmtpSessionManager},
|
||||
queue::{
|
||||
manager::Queue,
|
||||
manager::{Queue, SpawnQueue},
|
||||
spool::{QueuedMessages, SmtpSpool},
|
||||
},
|
||||
reporting::scheduler::SpawnReport,
|
||||
};
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use store::{
|
||||
RegistryStore, Store,
|
||||
RegistryStore, Store, ValueKey,
|
||||
registry::{bootstrap::Bootstrap, write::RegistryWrite},
|
||||
write::{AlignedBytes, Archive},
|
||||
};
|
||||
use tokio::sync::{mpsc, watch};
|
||||
use trc::EventType;
|
||||
use types::id::Id;
|
||||
use types::{collection::Collection, field::EmailField, id::Id};
|
||||
|
||||
pub struct TestServer {
|
||||
pub server: Server,
|
||||
pub accounts: AHashMap<&'static str, Account>,
|
||||
pub temp_dir: TempDir,
|
||||
pub queue_rx: mpsc::Receiver<QueueEvent>,
|
||||
pub report_rx: mpsc::Receiver<ReportingEvent>,
|
||||
shutdown_tx: watch::Sender<bool>,
|
||||
reset: bool,
|
||||
}
|
||||
@@ -67,8 +78,12 @@ pub struct TestServer {
|
||||
pub struct TestServerBuilder {
|
||||
bootstrap: Bootstrap,
|
||||
temp_dir: TempDir,
|
||||
http_listener_port: u16,
|
||||
reset: bool,
|
||||
logging_enabled: bool,
|
||||
capture_queue: bool,
|
||||
capture_reporting: bool,
|
||||
disable_services: bool,
|
||||
}
|
||||
|
||||
impl TestServerBuilder {
|
||||
@@ -99,9 +114,13 @@ impl TestServerBuilder {
|
||||
RegistryStore::new(&path, store, "mail.example.org".to_string(), 1, None).await,
|
||||
)
|
||||
.await,
|
||||
http_listener_port: 8899,
|
||||
temp_dir,
|
||||
reset,
|
||||
logging_enabled: false,
|
||||
capture_queue: false,
|
||||
capture_reporting: false,
|
||||
disable_services: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +146,21 @@ impl TestServerBuilder {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn with_http_listener(mut self, port: u16) -> Self {
|
||||
self.http_listener_port = port;
|
||||
self.with_listener(NetworkListenerProtocol::Http, "jmap", port, true)
|
||||
.await
|
||||
.with_object(Http {
|
||||
base_url: Expression {
|
||||
else_: format!("'https://127.0.0.1:{}'", port),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn with_listener(
|
||||
self,
|
||||
protocol: NetworkListenerProtocol,
|
||||
@@ -158,6 +192,21 @@ impl TestServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn capture_queue(mut self) -> Self {
|
||||
self.capture_queue = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn capture_reporting(mut self) -> Self {
|
||||
self.capture_reporting = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disable_services(mut self) -> Self {
|
||||
self.disable_services = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn insert_object(&self, object: impl Into<Object>) -> Id {
|
||||
self.bootstrap
|
||||
.registry
|
||||
@@ -244,8 +293,29 @@ impl TestServerBuilder {
|
||||
|
||||
// Start services
|
||||
self.bootstrap.assert_no_errors();
|
||||
ipc_rxs.spawn_queue_manager(inner.clone());
|
||||
ipc_rxs.spawn_services(inner.clone());
|
||||
if !self.disable_services {
|
||||
ipc_rxs.spawn_services(inner.clone());
|
||||
}
|
||||
|
||||
// Spawn queue manager if not capturing
|
||||
let (_, mut queue_rx) = mpsc::channel(100);
|
||||
let (_, mut report_rx) = mpsc::channel(100);
|
||||
if !self.capture_queue && !self.capture_reporting {
|
||||
ipc_rxs.spawn_queue_manager(inner.clone());
|
||||
} else {
|
||||
let queue_rx_ = ipc_rxs.queue_rx.take().unwrap();
|
||||
let report_rx_ = ipc_rxs.report_rx.take().unwrap();
|
||||
if !self.capture_queue {
|
||||
queue_rx_.spawn(inner.clone());
|
||||
} else {
|
||||
queue_rx = queue_rx_;
|
||||
}
|
||||
if !self.capture_reporting {
|
||||
report_rx_.spawn(inner.clone());
|
||||
} else {
|
||||
report_rx = report_rx_;
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn servers
|
||||
let (shutdown_tx, shutdown_rx) = servers.spawn(|server, acceptor, shutdown_rx| {
|
||||
@@ -284,17 +354,27 @@ impl TestServerBuilder {
|
||||
});
|
||||
|
||||
// Start broadcast subscriber
|
||||
spawn_broadcast_subscriber(inner.clone(), shutdown_rx);
|
||||
if !self.disable_services {
|
||||
spawn_broadcast_subscriber(inner.clone(), shutdown_rx);
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||
|
||||
let mut admin = Account::new(
|
||||
"admin",
|
||||
"popolna_zapora",
|
||||
&[],
|
||||
"Recovery Admin",
|
||||
Id::from(FALLBACK_ADMIN_ID),
|
||||
);
|
||||
admin.http_listener_port = self.http_listener_port;
|
||||
|
||||
TestServer {
|
||||
server: inner.build_server(),
|
||||
temp_dir: self.temp_dir,
|
||||
accounts: AHashMap::from_iter([(
|
||||
"admin",
|
||||
Account::new("admin", "popolna_zapora", &[], Id::from(FALLBACK_ADMIN_ID)),
|
||||
)]),
|
||||
accounts: AHashMap::from_iter([("admin", admin)]),
|
||||
queue_rx,
|
||||
report_rx,
|
||||
shutdown_tx,
|
||||
reset: self.reset,
|
||||
}
|
||||
@@ -302,6 +382,10 @@ impl TestServerBuilder {
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
pub fn reload_core(&mut self) {
|
||||
self.server = self.server.inner.build_server();
|
||||
}
|
||||
|
||||
pub fn account(&self, name: &str) -> &Account {
|
||||
self.accounts.get(name).unwrap()
|
||||
}
|
||||
@@ -339,6 +423,47 @@ impl TestServer {
|
||||
let _ = self.shutdown_tx.send(true);
|
||||
}
|
||||
|
||||
pub fn new_mta_session(&self) -> Session<DummyIo> {
|
||||
Session::test(self.server.clone())
|
||||
}
|
||||
|
||||
pub async fn resources(&self, name: &'static str, collection: Collection) -> Arc<DavResources> {
|
||||
let account_id = self.account(name).id().document_id();
|
||||
self.server
|
||||
.fetch_dav_resources(account_id, account_id, collection.into())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn fetch_email(&self, account_id: u32, document_id: u32) -> Vec<u8> {
|
||||
let metadata_ = self
|
||||
.server
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::property(
|
||||
account_id,
|
||||
Collection::Email,
|
||||
document_id,
|
||||
EmailField::Metadata,
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
self.server
|
||||
.blob_store()
|
||||
.get_blob(
|
||||
metadata_
|
||||
.unarchive::<MessageMetadata>()
|
||||
.unwrap()
|
||||
.blob_hash
|
||||
.0
|
||||
.as_slice(),
|
||||
0..usize::MAX,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn all_queued_messages(&self) -> QueuedMessages {
|
||||
self.server
|
||||
.next_event(&mut Queue::new(
|
||||
@@ -352,6 +477,23 @@ impl TestServer {
|
||||
self.wait_for_tasks().await;
|
||||
account.jmap_client().await.destroy_all_mailboxes().await;
|
||||
}
|
||||
|
||||
pub async fn inner_with_rxs(&self) -> (Arc<Inner>, IpcReceivers) {
|
||||
let (ipc, ipc_rxs) = build_ipc(false);
|
||||
|
||||
let mut bp = Bootstrap::new_uninitialized(self.server.registry().clone());
|
||||
|
||||
(
|
||||
Inner {
|
||||
shared_core: self.server.core.as_ref().clone().into_shared(),
|
||||
data: Default::default(),
|
||||
ipc,
|
||||
cache: Caches::parse(&mut bp).await,
|
||||
}
|
||||
.into(),
|
||||
ipc_rxs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Account {
|
||||
|
||||
104
tests/src/utils/sieve.rs
Normal file
104
tests/src/utils/sieve.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use imap_proto::ResponseType;
|
||||
use mail_send::smtp::tls::build_tls_connector;
|
||||
use rustls_pki_types::ServerName;
|
||||
use std::time::Duration;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio_rustls::client::TlsStream;
|
||||
|
||||
pub struct SieveConnection {
|
||||
reader: Lines<BufReader<ReadHalf<TlsStream<TcpStream>>>>,
|
||||
writer: WriteHalf<TlsStream<TcpStream>>,
|
||||
}
|
||||
|
||||
impl SieveConnection {
|
||||
pub async fn connect() -> Self {
|
||||
let (reader, writer) = tokio::io::split(
|
||||
build_tls_connector(true)
|
||||
.connect(
|
||||
ServerName::try_from("imap.example.org").unwrap().to_owned(),
|
||||
TcpStream::connect("127.0.0.1:4190").await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
SieveConnection {
|
||||
reader: BufReader::new(reader).lines(),
|
||||
writer,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate(&mut self, user: &str, pass: &str) {
|
||||
let creds = general_purpose::STANDARD.encode(format!("\0{user}\0{pass}"));
|
||||
self.send(&format!(
|
||||
"AUTHENTICATE PLAIN {{{}+}}\r\n{creds}",
|
||||
creds.len()
|
||||
))
|
||||
.await;
|
||||
self.assert_read(ResponseType::Ok).await;
|
||||
}
|
||||
|
||||
pub async fn assert_read(&mut self, rt: ResponseType) -> Vec<String> {
|
||||
let lines = self.read().await;
|
||||
let mut buf = Vec::with_capacity(10);
|
||||
rt.serialize(&mut buf);
|
||||
if lines
|
||||
.last()
|
||||
.unwrap()
|
||||
.starts_with(&String::from_utf8(buf).unwrap())
|
||||
{
|
||||
lines
|
||||
} else {
|
||||
panic!("Expected {:?} from server but got: {:?}", rt, lines);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read(&mut self) -> Vec<String> {
|
||||
let mut lines = Vec::new();
|
||||
loop {
|
||||
match tokio::time::timeout(Duration::from_millis(1500), self.reader.next_line()).await {
|
||||
Ok(Ok(Some(line))) => {
|
||||
let is_done =
|
||||
line.starts_with("OK") || line.starts_with("NO") || line.starts_with("BYE");
|
||||
//println!("<- {:?}", line);
|
||||
lines.push(line);
|
||||
if is_done {
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
Ok(Ok(None)) => {
|
||||
panic!("Invalid response: {:?}.", lines);
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
panic!("Connection broken: {} ({:?})", err, lines);
|
||||
}
|
||||
Err(_) => panic!("Timeout while waiting for server response: {:?}", lines),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, text: &str) {
|
||||
//println!("-> {:?}", text);
|
||||
self.writer.write_all(text.as_bytes()).await.unwrap();
|
||||
self.writer.write_all(b"\r\n").await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn send_raw(&mut self, text: &str) {
|
||||
//println!("-> {:?}", text);
|
||||
self.writer.write_all(text.as_bytes()).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn send_literal(&mut self, text: &str, literal: &str) {
|
||||
self.send(&format!("{}{{{}+}}\r\n{}", text, literal.len(), literal))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
37
tests/src/utils/temp_dir.rs
Normal file
37
tests/src/utils/temp_dir.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub struct TempDir {
|
||||
pub path: std::path::PathBuf,
|
||||
pub delete: bool,
|
||||
}
|
||||
|
||||
impl TempDir {
|
||||
pub fn new(name: &str, delete_if_exists: bool) -> Self {
|
||||
let mut path = std::env::temp_dir();
|
||||
path.push(name);
|
||||
if delete_if_exists && path.exists() {
|
||||
std::fs::remove_dir_all(&path).unwrap();
|
||||
}
|
||||
std::fs::create_dir_all(&path).unwrap();
|
||||
Self {
|
||||
path,
|
||||
delete: delete_if_exists,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&self) {
|
||||
std::fs::remove_dir_all(&self.path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
if self.delete {
|
||||
let _ = std::fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,18 @@ use store::rand::{Rng, distr::Alphanumeric, rng};
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct DummyWebDavClient {
|
||||
account_id: u32,
|
||||
name: &'static str,
|
||||
email: &'static str,
|
||||
credentials: String,
|
||||
pub account_id: u32,
|
||||
pub name: &'static str,
|
||||
pub email: &'static str,
|
||||
pub credentials: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DavResponse {
|
||||
headers: AHashMap<String, String>,
|
||||
status: StatusCode,
|
||||
body: Result<String, String>,
|
||||
xml: Vec<(String, String)>,
|
||||
pub headers: AHashMap<String, String>,
|
||||
pub status: StatusCode,
|
||||
pub body: Result<String, String>,
|
||||
pub xml: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -657,6 +657,10 @@ impl DavResponse {
|
||||
self.header("etag")
|
||||
}
|
||||
|
||||
pub fn lock_token(&self) -> &str {
|
||||
self.value("D:prop.D:lockdiscovery.D:activelock.D:locktoken.D:href")
|
||||
}
|
||||
|
||||
pub fn sync_token(&self) -> &str {
|
||||
self.find_keys("D:multistatus.D:sync-token")
|
||||
.next()
|
||||
|
||||
Reference in New Issue
Block a user