SieveScrtipts partially tested.
This commit is contained in:
@@ -41,3 +41,4 @@ serial_test = "2.0.0"
|
||||
sqlx = { version = "0.7.0-alpha.3", features = [ "runtime-tokio-rustls", "postgres", "mysql", "sqlite" ] }
|
||||
num_cpus = "1.15.0"
|
||||
async-trait = "0.1.68"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -47,7 +47,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
assert_eq!(
|
||||
server
|
||||
.get_document_ids(
|
||||
@@ -78,7 +78,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
assert_eq!(
|
||||
server
|
||||
.get_document_ids(
|
||||
@@ -120,7 +120,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
for (account_id, num_messages) in [(&account_id_1, 3), (&account_id_2, 1), (&account_id_3, 1)] {
|
||||
assert_eq!(
|
||||
server
|
||||
@@ -154,7 +154,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
for (account_id, num_messages) in [(&account_id_1, 3), (&account_id_2, 2), (&account_id_3, 2)] {
|
||||
assert_eq!(
|
||||
server
|
||||
@@ -192,7 +192,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
for (account_id, num_messages) in [(&account_id_1, 4), (&account_id_2, 3), (&account_id_3, 3)] {
|
||||
assert_eq!(
|
||||
server
|
||||
@@ -236,14 +236,16 @@ impl SmtpConnection {
|
||||
self.rcpt_to(recipient, 2).await;
|
||||
}
|
||||
self.data(3).await;
|
||||
self.data_bytes(message, recipients.len(), code).await
|
||||
let result = self.data_bytes(message, recipients.len(), code).await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn ingest(&mut self, from: &str, recipients: &[&str], message: &str) {
|
||||
self.ingest_with_code(from, recipients, message, 2).await;
|
||||
}
|
||||
|
||||
pub async fn ingest_chunked(
|
||||
async fn ingest_chunked(
|
||||
&mut self,
|
||||
from: &str,
|
||||
recipients: &[&str],
|
||||
@@ -258,6 +260,7 @@ impl SmtpConnection {
|
||||
self.bdat(std::str::from_utf8(chunk).unwrap(), 2).await;
|
||||
}
|
||||
self.bdat_last("", recipients.len(), 2).await;
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
pub async fn connect() -> Self {
|
||||
|
||||
628
tests/src/jmap/email_submission.rs
Normal file
628
tests/src/jmap/email_submission.rs
Normal file
@@ -0,0 +1,628 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use jmap::{JMAP, SUPERUSER_ID};
|
||||
use jmap_client::{
|
||||
client::Client,
|
||||
core::set::{SetError, SetErrorType, SetObject},
|
||||
email_submission::{Address, Delivered, DeliveryStatus, Displayed, UndoStatus},
|
||||
mailbox::Role,
|
||||
Error,
|
||||
};
|
||||
use jmap_proto::types::id::Id;
|
||||
use mail_parser::DateTime;
|
||||
use store::parking_lot::Mutex;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
net::TcpListener,
|
||||
sync::mpsc,
|
||||
};
|
||||
|
||||
use crate::jmap::email_set::assert_email_properties;
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct MockMessage {
|
||||
pub mail_from: String,
|
||||
pub rcpt_to: Vec<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl MockMessage {
|
||||
pub fn new<T, U>(mail_from: T, rcpt_to: U, message: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: IntoIterator<Item = T>,
|
||||
{
|
||||
Self {
|
||||
mail_from: mail_from.into(),
|
||||
rcpt_to: rcpt_to.into_iter().map(|s| s.into()).collect(),
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockSMTPSettings {
|
||||
pub fail_mail_from: bool,
|
||||
pub fail_rcpt_to: bool,
|
||||
pub fail_message: bool,
|
||||
pub do_stop: bool,
|
||||
}
|
||||
|
||||
const TEST_DKIM_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
|
||||
jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb
|
||||
to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB
|
||||
AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX
|
||||
/1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ
|
||||
gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO
|
||||
n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m
|
||||
3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/
|
||||
eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj
|
||||
7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA
|
||||
qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf
|
||||
eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX
|
||||
GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
|
||||
-----END RSA PRIVATE KEY-----"#;
|
||||
|
||||
#[allow(clippy::disallowed_types)]
|
||||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running E-mail submissions tests...");
|
||||
// Start mock SMTP server
|
||||
let (mut smtp_rx, smtp_settings) = spawn_mock_smtp_server();
|
||||
|
||||
// Create an identity without using a valid address should fail
|
||||
match client
|
||||
.set_default_account_id(Id::new(1).to_string())
|
||||
.identity_create("John Doe", "jdoe@example.com")
|
||||
.await
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::Set(err) => assert_eq!(err.error(), &SetErrorType::InvalidProperties),
|
||||
err => panic!("Unexpected error: {:?}", err),
|
||||
}
|
||||
|
||||
// Create a domain and a test account
|
||||
let domain_id = client
|
||||
.set_default_account_id(Id::new(0))
|
||||
.domain_create("example.com")
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
let account_id = client
|
||||
.individual_create("jdoe@example.com", "12345", "John Doe")
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
let identity_id = client
|
||||
.set_default_account_id(&account_id)
|
||||
.identity_create("John Doe", "jdoe@example.com")
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
// Create test mailboxes
|
||||
let mailbox_id = client
|
||||
.mailbox_create("JMAP EmailSubmission", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
let mailbox_id_2 = client
|
||||
.mailbox_create("JMAP EmailSubmission 2", None::<String>, Role::None)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
// Import an email without any recipients
|
||||
let email_id = client
|
||||
.email_import(
|
||||
b"From: jdoe@example.com\nSubject: hey\n\ntest".to_vec(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<&str>>,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
// Submission without a valid emailId or identityId should fail
|
||||
assert!(matches!(
|
||||
client
|
||||
.email_submission_create(Id::new(123456).to_string(), &identity_id)
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::InvalidProperties,
|
||||
..
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
client
|
||||
.email_submission_create(&email_id, Id::new(123456).to_string())
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::InvalidProperties,
|
||||
..
|
||||
}))
|
||||
));
|
||||
|
||||
// Submissions of e-mails without any recipients should fail
|
||||
assert!(matches!(
|
||||
client
|
||||
.email_submission_create(&email_id, &identity_id)
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::InvalidProperties,
|
||||
..
|
||||
}))
|
||||
));
|
||||
|
||||
// Submissions with an envelope that does not match
|
||||
// the identity from address should fail
|
||||
assert!(matches!(
|
||||
client
|
||||
.email_submission_create_envelope(
|
||||
&email_id,
|
||||
&identity_id,
|
||||
"other_address@example.com",
|
||||
Vec::<Address>::new(),
|
||||
)
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::InvalidProperties,
|
||||
..
|
||||
}))
|
||||
));
|
||||
|
||||
// Submit a valid message submission
|
||||
let email_body =
|
||||
"From: jdoe@example.com\r\nTo: jane_smith@example.com\r\nSubject: hey\r\n\r\ntest";
|
||||
let email_id = client
|
||||
.email_import(
|
||||
email_body.as_bytes().to_vec(),
|
||||
[&mailbox_id],
|
||||
None::<Vec<&str>>,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
client
|
||||
.email_submission_create(&email_id, &identity_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Confirm that the message has been delivered
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com>",
|
||||
["<jane_smith@example.com>"],
|
||||
email_body,
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Manually add recipients to the envelope and confirm submission
|
||||
let email_submission_id = client
|
||||
.email_submission_create_envelope(
|
||||
&email_id,
|
||||
&identity_id,
|
||||
"jdoe@example.com",
|
||||
[
|
||||
"tim@foobar.com", // Should be de-duplicated
|
||||
"tim@foobar.com",
|
||||
"tim@foobar.com ",
|
||||
" james@other_domain.com ", // Should be sanitized
|
||||
" secret_rcpt@test.com ",
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com>",
|
||||
[
|
||||
"<james@other_domain.com>",
|
||||
"<secret_rcpt@test.com>",
|
||||
"<tim@foobar.com>",
|
||||
],
|
||||
email_body,
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Confirm that the email submission status was updated
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let email_submission = client
|
||||
.email_submission_get(&email_submission_id, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(email_submission.undo_status().unwrap(), &UndoStatus::Final);
|
||||
assert_eq!(
|
||||
email_submission.delivery_status().unwrap(),
|
||||
&AHashMap::from_iter([
|
||||
(
|
||||
"tim@foobar.com".to_string(),
|
||||
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
|
||||
),
|
||||
(
|
||||
"secret_rcpt@test.com".to_string(),
|
||||
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
|
||||
),
|
||||
(
|
||||
"james@other_domain.com".to_string(),
|
||||
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
// SMTP rejects some of the recipients
|
||||
smtp_settings.lock().fail_rcpt_to = true;
|
||||
let email_submission_id = client
|
||||
.email_submission_create_envelope(
|
||||
&email_id,
|
||||
&identity_id,
|
||||
"jdoe@example.com",
|
||||
["tim@foobar.com", "james@other_domain.com", "jane@test.com"],
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new("<jdoe@example.com>", ["<tim@foobar.com>"], email_body),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Confirm that all delivery failures were included
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let email_submission = client
|
||||
.email_submission_get(&email_submission_id, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(email_submission.undo_status().unwrap(), &UndoStatus::Final);
|
||||
assert_eq!(
|
||||
email_submission.delivery_status().unwrap(),
|
||||
&AHashMap::from_iter([
|
||||
(
|
||||
"james@other_domain.com".to_string(),
|
||||
DeliveryStatus::new(
|
||||
"550 I refuse to accept that recipient.",
|
||||
Delivered::No,
|
||||
Displayed::Unknown
|
||||
)
|
||||
),
|
||||
(
|
||||
"jane@test.com".to_string(),
|
||||
DeliveryStatus::new(
|
||||
"550 I refuse to accept that recipient.",
|
||||
Delivered::No,
|
||||
Displayed::Unknown
|
||||
)
|
||||
),
|
||||
(
|
||||
"tim@foobar.com".to_string(),
|
||||
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
|
||||
),
|
||||
])
|
||||
);
|
||||
smtp_settings.lock().fail_rcpt_to = false;
|
||||
|
||||
// SMTP rejects the message
|
||||
smtp_settings.lock().fail_message = true;
|
||||
let email_submission_id = client
|
||||
.email_submission_create_envelope(
|
||||
&email_id,
|
||||
&identity_id,
|
||||
"jdoe@example.com",
|
||||
["tim@foobar.com", "james@other_domain.com", "jane@test.com"],
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
expect_nothing(&mut smtp_rx).await;
|
||||
|
||||
// Confirm that all delivery failures were included
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let email_submission = client
|
||||
.email_submission_get(&email_submission_id, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
email_submission.undo_status().unwrap(),
|
||||
&UndoStatus::Canceled
|
||||
);
|
||||
assert_eq!(
|
||||
email_submission.delivery_status().unwrap(),
|
||||
&AHashMap::from_iter([
|
||||
(
|
||||
"james@other_domain.com".to_string(),
|
||||
DeliveryStatus::new(
|
||||
"503 Thank you but I am saving myself for dessert.",
|
||||
Delivered::No,
|
||||
Displayed::Unknown
|
||||
)
|
||||
),
|
||||
(
|
||||
"jane@test.com".to_string(),
|
||||
DeliveryStatus::new(
|
||||
"503 Thank you but I am saving myself for dessert.",
|
||||
Delivered::No,
|
||||
Displayed::Unknown
|
||||
)
|
||||
),
|
||||
(
|
||||
"tim@foobar.com".to_string(),
|
||||
DeliveryStatus::new(
|
||||
"503 Thank you but I am saving myself for dessert.",
|
||||
Delivered::No,
|
||||
Displayed::Unknown
|
||||
)
|
||||
),
|
||||
])
|
||||
);
|
||||
smtp_settings.lock().fail_message = false;
|
||||
|
||||
// Enable DKIM for the domain
|
||||
client
|
||||
.set_default_account_id(Id::from(SUPERUSER_ID))
|
||||
.domain_enable_dkim(&domain_id, TEST_DKIM_KEY, "my-selector", None)
|
||||
.await
|
||||
.unwrap();
|
||||
client.set_default_account_id(&account_id);
|
||||
|
||||
// Confirm that the sendAt property is updated when using FUTURERELEASE
|
||||
let email_submission_id = client
|
||||
.email_submission_create_envelope(
|
||||
&email_id,
|
||||
&identity_id,
|
||||
Address::new("jdoe@example.com").parameter("HOLDUNTIL", Some("2079-11-20T05:00:00Z")),
|
||||
["jane_smith@example.com"],
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com> HOLDUNTIL=2079-11-20T05:00:00Z",
|
||||
["<jane_smith@example.com>"],
|
||||
email_body,
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let email_submission = client
|
||||
.email_submission_get(&email_submission_id, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
email_submission.send_at().unwrap(),
|
||||
DateTime::parse_rfc3339("2079-11-20T05:00:00Z")
|
||||
.unwrap()
|
||||
.to_timestamp()
|
||||
);
|
||||
|
||||
// Verify onSuccessUpdateEmail action
|
||||
let mut request = client.build();
|
||||
let set_request = request.set_email_submission();
|
||||
let create_id = set_request
|
||||
.create()
|
||||
.email_id(&email_id)
|
||||
.identity_id(&identity_id)
|
||||
.create_id()
|
||||
.unwrap();
|
||||
set_request
|
||||
.arguments()
|
||||
.on_success_update_email(&create_id)
|
||||
.keyword("$draft", true)
|
||||
.mailbox_id(&mailbox_id, false)
|
||||
.mailbox_id(&mailbox_id_2, true);
|
||||
request.send().await.unwrap().unwrap_method_responses();
|
||||
|
||||
assert_email_properties(client, &email_id, &[&mailbox_id_2], &["$draft"]).await;
|
||||
|
||||
// Verify onSuccessDestroyEmail action
|
||||
smtp_settings.lock().do_stop = true;
|
||||
let mut request = client.build();
|
||||
let set_request = request.set_email_submission();
|
||||
let create_id = set_request
|
||||
.create()
|
||||
.email_id(&email_id)
|
||||
.identity_id(&identity_id)
|
||||
.create_id()
|
||||
.unwrap();
|
||||
set_request.arguments().on_success_destroy_email(&create_id);
|
||||
request.send().await.unwrap().unwrap_method_responses();
|
||||
|
||||
assert!(client
|
||||
.email_get(&email_id, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// Destroy the created mailbox, identity and all submissions
|
||||
let todo = "true";
|
||||
/*client
|
||||
.set_default_account_id(Id::from(SUPERUSER_ID))
|
||||
.principal_destroy(&account_id)
|
||||
.await
|
||||
.unwrap();
|
||||
client.principal_destroy(&domain_id).await.unwrap();
|
||||
server.store.principal_purge().unwrap();
|
||||
server.store.assert_is_empty();*/
|
||||
}
|
||||
|
||||
pub fn spawn_mock_smtp_server() -> (mpsc::Receiver<MockMessage>, Arc<Mutex<MockSMTPSettings>>) {
|
||||
// Create channels
|
||||
let (event_tx, event_rx) = mpsc::channel::<MockMessage>(100);
|
||||
let _settings = Arc::new(Mutex::new(MockSMTPSettings::default()));
|
||||
let settings = _settings.clone();
|
||||
|
||||
// Start mock SMTP server
|
||||
tokio::spawn(async move {
|
||||
let listener = TcpListener::bind("127.0.0.1:9999")
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Failed to bind mock SMTP server to 127.0.0.1:9999: {}", e);
|
||||
});
|
||||
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
let (rx, mut tx) = stream.split();
|
||||
let mut rx = BufReader::new(rx);
|
||||
let mut buf = String::with_capacity(128);
|
||||
let mut message = MockMessage::default();
|
||||
|
||||
tx.write_all(b"220 [127.0.0.1] Clueless host service ready\r\n")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
while rx.read_line(&mut buf).await.is_ok() {
|
||||
print!("-> {}", buf);
|
||||
if buf.starts_with("EHLO") {
|
||||
tx.write_all(b"250 Hi there, but I have no extensions to offer :-(\r\n")
|
||||
.await
|
||||
.unwrap();
|
||||
} else if buf.starts_with("MAIL FROM") {
|
||||
if settings.lock().fail_mail_from {
|
||||
tx.write_all("552-I do not\r\n552 like that MAIL FROM.\r\n".as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
message.mail_from = buf.split_once(':').unwrap().1.trim().to_string();
|
||||
tx.write_all(b"250 OK\r\n").await.unwrap();
|
||||
}
|
||||
} else if buf.starts_with("RCPT TO") {
|
||||
if settings.lock().fail_rcpt_to && !buf.contains("foobar.com") {
|
||||
tx.write_all(
|
||||
"550-I refuse to\r\n550 accept that recipient.\r\n".as_bytes(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
message
|
||||
.rcpt_to
|
||||
.push(buf.split(':').nth(1).unwrap().trim().to_string());
|
||||
tx.write_all(b"250 OK\r\n").await.unwrap();
|
||||
}
|
||||
} else if buf.starts_with("DATA") {
|
||||
if settings.lock().fail_message {
|
||||
tx.write_all(
|
||||
"503-Thank you but I am\r\n503 saving myself for dessert.\r\n"
|
||||
.as_bytes(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
} else if !message.mail_from.is_empty() && !message.rcpt_to.is_empty() {
|
||||
tx.write_all(b"354 Start feeding me now some quality content please\r\n")
|
||||
.await
|
||||
.unwrap();
|
||||
buf.clear();
|
||||
while rx.read_line(&mut buf).await.is_ok() {
|
||||
if buf.starts_with('.') {
|
||||
message.message = message.message.trim().to_string();
|
||||
break;
|
||||
} else {
|
||||
message.message += &buf;
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
tx.write_all(b"250 Great success!\r\n").await.unwrap();
|
||||
message.rcpt_to.sort_unstable();
|
||||
event_tx.send(message).await.unwrap();
|
||||
message = MockMessage::default();
|
||||
} else {
|
||||
tx.write_all("554 You forgot to tell me a few things.\r\n".as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
} else if buf.starts_with("QUIT") {
|
||||
tx.write_all("250 Arrivederci!\r\n".as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
break;
|
||||
} else if buf.starts_with("RSET") {
|
||||
tx.write_all("250 Your wish is my command.\r\n".as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
message = MockMessage::default();
|
||||
} else {
|
||||
println!("Unknown command: {}", buf.trim());
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if settings.lock().do_stop {
|
||||
println!("Mock SMTP server stopped.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(event_rx, _settings)
|
||||
}
|
||||
|
||||
pub async fn assert_message_delivery(
|
||||
event_rx: &mut mpsc::Receiver<MockMessage>,
|
||||
expected_message: MockMessage,
|
||||
expect_dkim: bool,
|
||||
) {
|
||||
match tokio::time::timeout(Duration::from_millis(3000), event_rx.recv()).await {
|
||||
Ok(Some(message)) => {
|
||||
assert_eq!(message.mail_from, expected_message.mail_from);
|
||||
assert_eq!(message.rcpt_to, expected_message.rcpt_to);
|
||||
|
||||
println!("Got message [{}]", message.message);
|
||||
|
||||
if let Some(needle) = expected_message.message.strip_prefix('@') {
|
||||
assert!(
|
||||
message.message.contains(needle),
|
||||
"[{}] needle = {:?}",
|
||||
message.message,
|
||||
needle
|
||||
);
|
||||
} else {
|
||||
let message = if expect_dkim {
|
||||
if message.message.starts_with("DKIM-Signature:") {
|
||||
message.message.split_once('\n').unwrap().1
|
||||
} else {
|
||||
panic!(
|
||||
"Expected DKIM-Signature header but got: {}",
|
||||
message.message
|
||||
);
|
||||
}
|
||||
} else {
|
||||
&message.message
|
||||
};
|
||||
|
||||
assert_eq!(message, expected_message.message);
|
||||
}
|
||||
}
|
||||
result => {
|
||||
panic!(
|
||||
"Timeout waiting for message {:?}: {:?}",
|
||||
expected_message, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn expect_nothing(event_rx: &mut mpsc::Receiver<MockMessage>) {
|
||||
match tokio::time::timeout(Duration::from_millis(500), event_rx.recv()).await {
|
||||
Err(_) => {}
|
||||
message => {
|
||||
panic!("Received a message when expecting nothing: {:?}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,14 @@ pub mod email_query;
|
||||
pub mod email_query_changes;
|
||||
pub mod email_search_snippet;
|
||||
pub mod email_set;
|
||||
pub mod email_submission;
|
||||
pub mod event_source;
|
||||
pub mod mailbox;
|
||||
pub mod push_subscription;
|
||||
pub mod sieve_script;
|
||||
pub mod thread_get;
|
||||
pub mod thread_merge;
|
||||
pub mod vacation_response;
|
||||
|
||||
const SERVER: &str = r#"
|
||||
[server]
|
||||
@@ -123,6 +126,7 @@ address = "sqlite::memory:"
|
||||
uid-by-login = "SELECT ROWID - 1 FROM users WHERE login = ?"
|
||||
login-by-uid = "SELECT login FROM users WHERE ROWID - 1 = ?"
|
||||
secret-by-uid = "SELECT secret FROM users WHERE ROWID - 1 = ?"
|
||||
name-by-uid = "SELECT name FROM users WHERE ROWID - 1 = ?"
|
||||
gids-by-uid = "SELECT gid FROM groups WHERE uid = ?"
|
||||
uids-by-address = "SELECT uid FROM emails WHERE email = ?"
|
||||
addresses-by-uid = "SELECT email FROM emails WHERE uid = ?"
|
||||
@@ -166,8 +170,11 @@ pub async fn jmap_tests() {
|
||||
//auth_acl::test(params.server.clone(), &mut params.client).await;
|
||||
//auth_limits::test(params.server.clone(), &mut params.client).await;
|
||||
//auth_oauth::test(params.server.clone(), &mut params.client).await;
|
||||
event_source::test(params.server.clone(), &mut params.client).await;
|
||||
//event_source::test(params.server.clone(), &mut params.client).await;
|
||||
//push_subscription::test(params.server.clone(), &mut params.client).await;
|
||||
sieve_script::test(params.server.clone(), &mut params.client).await;
|
||||
|
||||
let websockets = "todo";
|
||||
|
||||
if delete {
|
||||
params.temp_dir.delete();
|
||||
@@ -197,7 +204,7 @@ async fn init_jmap_tests(delete_if_exists: bool) -> JMAPTest {
|
||||
let smtp = SMTP::init(&config, &servers, delivery_tx)
|
||||
.await
|
||||
.failed("Invalid configuration file");
|
||||
let jmap = JMAP::init(&config, delivery_rx)
|
||||
let jmap = JMAP::init(&config, delivery_rx, smtp.clone())
|
||||
.await
|
||||
.failed("Invalid configuration file");
|
||||
let shutdown_tx = servers.spawn(|server, shutdown_rx| {
|
||||
|
||||
460
tests/src/jmap/sieve_script.rs
Normal file
460
tests/src/jmap/sieve_script.rs
Normal file
@@ -0,0 +1,460 @@
|
||||
use std::{fs, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use jmap::{JMAP, SUPERUSER_ID};
|
||||
use jmap_client::{
|
||||
client::Client,
|
||||
core::set::{SetError, SetErrorType},
|
||||
email, mailbox,
|
||||
sieve::query::{Comparator, Filter},
|
||||
Error,
|
||||
};
|
||||
use jmap_proto::types::id::Id;
|
||||
|
||||
use crate::jmap::{
|
||||
delivery::SmtpConnection,
|
||||
email_submission::{assert_message_delivery, spawn_mock_smtp_server, MockMessage},
|
||||
test_account_create,
|
||||
};
|
||||
use crate::smtp::session::VerifyResponse;
|
||||
|
||||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running Sieve tests...");
|
||||
|
||||
// Create test account
|
||||
let account_id = test_account_create(&server, "jdoe@example.com", "12345", "John Doe")
|
||||
.await
|
||||
.to_string();
|
||||
client.set_default_account_id(&account_id);
|
||||
|
||||
// Validate scripts
|
||||
client
|
||||
.sieve_script_validate(get_script("validate_ok"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
client
|
||||
.sieve_script_validate(get_script("validate_error"))
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::InvalidScript,
|
||||
..
|
||||
}))
|
||||
));
|
||||
|
||||
// Create 5 Sieve scripts, all deactivated.
|
||||
let mut script_ids = Vec::new();
|
||||
for i in 0..5 {
|
||||
script_ids.push(
|
||||
client
|
||||
.sieve_script_create(
|
||||
format!("script_{}", i + 1),
|
||||
format!("require \"fileinto\"; fileinto \"{}\";", i + 1).into_bytes(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id(),
|
||||
);
|
||||
}
|
||||
|
||||
let response = client
|
||||
.sieve_script_query(Filter::is_active(false).into(), [Comparator::name()].into())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.ids().len(), 5);
|
||||
for (pos, id) in response.ids().iter().enumerate() {
|
||||
let script = client
|
||||
.sieve_script_get(id, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(script.name().unwrap(), format!("script_{}", pos + 1));
|
||||
assert_eq!(
|
||||
String::from_utf8(client.download(script.blob_id().unwrap()).await.unwrap()).unwrap(),
|
||||
format!("require \"fileinto\"; fileinto \"{}\";", pos + 1)
|
||||
);
|
||||
}
|
||||
|
||||
// Activate last script twice and then the first script
|
||||
for _ in 0..2 {
|
||||
client
|
||||
.sieve_script_activate(script_ids.last().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.sieve_script_query(Filter::is_active(true).into(), [Comparator::name()].into())
|
||||
.await
|
||||
.unwrap()
|
||||
.ids(),
|
||||
vec![script_ids.last().unwrap().to_string()]
|
||||
);
|
||||
}
|
||||
client
|
||||
.sieve_script_activate(script_ids.first().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.sieve_script_query(Filter::is_active(true).into(), [Comparator::name()].into())
|
||||
.await
|
||||
.unwrap()
|
||||
.ids(),
|
||||
vec![script_ids.first().unwrap().to_string()]
|
||||
);
|
||||
|
||||
// Destroying an active script should not work
|
||||
assert!(matches!(
|
||||
client
|
||||
.sieve_script_destroy(script_ids.first().unwrap())
|
||||
.await,
|
||||
Err(Error::Set(SetError {
|
||||
type_: SetErrorType::ScriptIsActive,
|
||||
..
|
||||
}))
|
||||
));
|
||||
|
||||
// Deactivate all scripts
|
||||
client.sieve_script_deactivate().await.unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.sieve_script_query(Filter::is_active(true).into(), [Comparator::name()].into())
|
||||
.await
|
||||
.unwrap()
|
||||
.ids(),
|
||||
Vec::<String>::new()
|
||||
);
|
||||
|
||||
// Connect to LMTP service
|
||||
let mut lmtp = SmtpConnection::connect().await;
|
||||
|
||||
// Run mailbox, fileinto, flags tests
|
||||
client
|
||||
.sieve_script_create("test_mailbox", get_script("test_mailbox"), true)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: TPS Report\r\n",
|
||||
"\r\n",
|
||||
"I'm going to need those TPS reports ASAP. ",
|
||||
"So, if you could do that, that'd be great."
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Make sure all folders were created
|
||||
let mailbox_names = "My/Nested/Mailbox/with/multiple/levels/Folder"
|
||||
.split('/')
|
||||
.collect::<Vec<_>>();
|
||||
let mut mailbox_ids = Vec::new();
|
||||
for &mailbox in &mailbox_names {
|
||||
let mut response = client
|
||||
.mailbox_query(mailbox::query::Filter::name(mailbox).into(), None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
!response.ids().is_empty(),
|
||||
"Mailbox {} was not created.",
|
||||
mailbox
|
||||
);
|
||||
mailbox_ids.extend(response.take_ids());
|
||||
}
|
||||
assert_eq!(mailbox_ids.len(), mailbox_names.len());
|
||||
|
||||
// Make sure the message was delivered to the right folders
|
||||
let message_ids = client
|
||||
.email_query(None::<email::query::Filter>, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_ids();
|
||||
assert_eq!(message_ids.len(), 1, "too many messages {:?}", message_ids);
|
||||
let email = client
|
||||
.email_get(
|
||||
message_ids.last().unwrap(),
|
||||
[email::Property::MailboxIds, email::Property::Keywords].into(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
email.keywords().len(),
|
||||
2,
|
||||
"Expected 2 keywords, found {:?}.",
|
||||
email.keywords()
|
||||
);
|
||||
for keyword in ["$important", "$seen"] {
|
||||
if !email.keywords().contains(&keyword) {
|
||||
panic!("Keyword {} not found in {:?}.", keyword, email.keywords());
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
email.mailbox_ids().len(),
|
||||
2,
|
||||
"Expected 2 mailbox ids, found {:?}.",
|
||||
email.mailbox_ids()
|
||||
);
|
||||
for mailbox_pos in [mailbox_ids.len() - 1, mailbox_ids.len() - 2] {
|
||||
if !email
|
||||
.mailbox_ids()
|
||||
.contains(&mailbox_ids[mailbox_pos].as_str())
|
||||
{
|
||||
panic!(
|
||||
"Mailbox {} ({}) not found in {:?}.",
|
||||
mailbox_names[mailbox_pos],
|
||||
mailbox_ids[mailbox_pos],
|
||||
email.keywords()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Run discard and duplicate tests
|
||||
client
|
||||
.sieve_script_create(
|
||||
"test_discard_reject",
|
||||
get_script("test_discard_reject"),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"Bcc: Undisclosed recipients;\r\n",
|
||||
"Message-ID: <1234@example.com>\r\n",
|
||||
"Subject: Holidays\r\n",
|
||||
"\r\n",
|
||||
"Remember to file your TPS reports before ",
|
||||
"going on holidays."
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
client
|
||||
.email_query(None::<email::query::Filter>, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.ids()
|
||||
.len(),
|
||||
1,
|
||||
"Discard failed."
|
||||
);
|
||||
|
||||
// Let one sec duplicate ids expire
|
||||
tokio::time::sleep(Duration::from_millis(1100)).await;
|
||||
|
||||
// Run reject and duplicate check tests
|
||||
let test = "fd";
|
||||
/*lmtp.ingest_with_code(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"Bcc: Undisclosed recipients;\r\n",
|
||||
"Message-ID: <1234@example.com>\r\n",
|
||||
"Subject: Holidays\r\n",
|
||||
"\r\n",
|
||||
"Remember to file your T.P.S. reports before ",
|
||||
"going on holidays."
|
||||
),
|
||||
5,
|
||||
)
|
||||
.await
|
||||
.assert_contains("No soup for you");
|
||||
assert_eq!(
|
||||
client
|
||||
.email_query(None::<email::query::Filter>, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.ids()
|
||||
.len(),
|
||||
1,
|
||||
"Reject failed."
|
||||
);*/
|
||||
|
||||
// Run include tests
|
||||
client
|
||||
.sieve_script_create("test_include_this", get_script("test_include_this"), false)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.sieve_script_create("test_include", get_script("test_include"), true)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest_with_code(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"Bcc: Undisclosed recipients;\r\n",
|
||||
"Message-ID: <1234@example.com>\r\n",
|
||||
"Subject: Holidays\r\n",
|
||||
"\r\n",
|
||||
"Remember to file your T.P.S. reports before ",
|
||||
"going on holidays."
|
||||
),
|
||||
5,
|
||||
)
|
||||
.await
|
||||
.assert_contains("Rejected from an included script");
|
||||
|
||||
// Start mock SMTP server
|
||||
let coco = "fd";
|
||||
/*let (mut smtp_rx, smtp_settings) = spawn_mock_smtp_server();
|
||||
|
||||
// Run enclose + redirect tests
|
||||
client
|
||||
.sieve_script_create(
|
||||
"test_redirect_enclose",
|
||||
get_script("test_redirect_enclose"),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: TPS Report\r\n",
|
||||
"\r\n",
|
||||
"I'm going to need those TPS reports ASAP. ",
|
||||
"So, if you could do that, that'd be great."
|
||||
),
|
||||
)
|
||||
.await;
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com>",
|
||||
["<jane@example.com>"],
|
||||
"@Attached you'll find",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
client
|
||||
.email_query(None::<email::query::Filter>, None::<Vec<_>>)
|
||||
.await
|
||||
.unwrap()
|
||||
.ids()
|
||||
.len(),
|
||||
1,
|
||||
"Redirected message was stored."
|
||||
);
|
||||
|
||||
// Run notify + editheader + notify + fcc tests
|
||||
client
|
||||
.sieve_script_create("test_notify_fcc", get_script("test_notify_fcc"), true)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: Urgently I need those TPS Reports\r\n",
|
||||
"\r\n",
|
||||
"I'm going to need those TPS reports ASAP. ",
|
||||
"So, if you could do that, that'd be great."
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com>",
|
||||
["<sms_gateway@example.com>"],
|
||||
"@It's TPS-o-clock",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut request = client.build();
|
||||
request.get_email().properties([
|
||||
email::Property::MailboxIds,
|
||||
email::Property::Keywords,
|
||||
email::Property::Subject,
|
||||
]);
|
||||
let emails = request.send_get_email().await.unwrap().take_list();
|
||||
|
||||
assert_eq!(
|
||||
emails.len(),
|
||||
3,
|
||||
"Two new messages were expected: {:#?}.",
|
||||
emails
|
||||
);
|
||||
|
||||
'outer: for (subject, folder, keywords) in [
|
||||
("It's TPS-o-clock", "Notifications", ""),
|
||||
(
|
||||
"Urgently I need those **censored** Reports",
|
||||
"Inbox",
|
||||
"$seen",
|
||||
),
|
||||
] {
|
||||
for email in &emails {
|
||||
if email.subject().unwrap().eq(subject) {
|
||||
if !keywords.is_empty() && !email.keywords().contains(&keywords) {
|
||||
panic!("Keyword {:?} not found in: {:#?}", keywords, email);
|
||||
}
|
||||
|
||||
let mailbox_id = client
|
||||
.mailbox_query(
|
||||
mailbox::query::Filter::name(folder.to_string()).into(),
|
||||
None::<Vec<_>>,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.take_ids()
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("Mailbox {:?} not found", folder));
|
||||
|
||||
if !email.mailbox_ids().contains(&mailbox_id.as_str()) {
|
||||
panic!(
|
||||
"Mailbox {:?} ({}) not found in: {:#?}",
|
||||
folder, mailbox_id, email
|
||||
);
|
||||
}
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
panic!("Email {:?} not found in: {:#?}", subject, emails);
|
||||
}
|
||||
|
||||
smtp_settings.lock().do_stop = true;
|
||||
|
||||
*/
|
||||
|
||||
// Remove test data
|
||||
let todo = "fix";
|
||||
/*for account_id in [&account_id, &domain_id] {
|
||||
client
|
||||
.set_default_account_id(Id::new(SUPERUSER_ID as u64))
|
||||
.principal_destroy(account_id)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
server.store.principal_purge().unwrap();
|
||||
server.store.assert_is_empty();*/
|
||||
}
|
||||
|
||||
fn get_script(name: &str) -> Vec<u8> {
|
||||
let mut script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
script_path.push("resources");
|
||||
script_path.push("jmap_sieve");
|
||||
script_path.push(format!("{}.sieve", name));
|
||||
fs::read(script_path).unwrap()
|
||||
}
|
||||
167
tests/src/jmap/vacation_response.rs
Normal file
167
tests/src/jmap/vacation_response.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use jmap::{JMAP, SUPERUSER_ID};
|
||||
use jmap_client::client::Client;
|
||||
use jmap_proto::types::id::Id;
|
||||
|
||||
use crate::jmap::{
|
||||
delivery::SmtpConnection,
|
||||
email_submission::{
|
||||
assert_message_delivery, expect_nothing, spawn_mock_smtp_server, MockMessage,
|
||||
},
|
||||
};
|
||||
|
||||
pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||
println!("Running Vacation Response tests...");
|
||||
|
||||
// Create INBOX
|
||||
let domain_id = client
|
||||
.set_default_account_id(Id::new(SUPERUSER_ID as u64))
|
||||
.domain_create("example.com")
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
let account_id = client
|
||||
.individual_create("jdoe@example.com", "12345", "John Doe")
|
||||
.await
|
||||
.unwrap()
|
||||
.take_id();
|
||||
|
||||
// Start mock SMTP server
|
||||
let (mut smtp_rx, smtp_settings) = spawn_mock_smtp_server();
|
||||
|
||||
// Let people know that we'll be down in Kokomo
|
||||
client
|
||||
.set_default_account_id(&account_id)
|
||||
.vacation_response_create(
|
||||
"Off the Florida Keys there's a place called Kokomo",
|
||||
"That's where you wanna go to get away from it all".into(),
|
||||
"That's where <b>you wanna go</b> to get away from it all".into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Connect to LMTP service
|
||||
let mut lmtp = SmtpConnection::connect().await;
|
||||
|
||||
// Send a message
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: TPS Report\r\n",
|
||||
"\r\n",
|
||||
"I'm going to need those TPS reports ASAP. ",
|
||||
"So, if you could do that, that'd be great."
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Await vacation response
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new("<jdoe@example.com>", ["<bill@example.com>"], "@Kokomo"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Further messages from the same recipient should not
|
||||
// trigger a vacation response
|
||||
lmtp.ingest(
|
||||
"bill@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: bill@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: TPS Report -- friendly reminder\r\n",
|
||||
"\r\n",
|
||||
"Listen, are you gonna have those TPS reports for us this afternoon?",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_nothing(&mut smtp_rx).await;
|
||||
|
||||
// Messages from MAILER-DAEMON should not
|
||||
// trigger a vacation response
|
||||
lmtp.ingest(
|
||||
"MAILER-DAEMON@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: MAILER-DAEMON@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: Delivery Failure\r\n",
|
||||
"\r\n",
|
||||
"I tried so hard and got so far but in the end it wasn't delivered.",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_nothing(&mut smtp_rx).await;
|
||||
|
||||
// Vacation responses should honor the configured date ranges
|
||||
client
|
||||
.vacation_response_set_dates((Utc::now() + Duration::days(1)).timestamp().into(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
lmtp.ingest(
|
||||
"jane_smith@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: jane_smith@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: When were you going on holidays?\r\n",
|
||||
"\r\n",
|
||||
"I'm asking because Bill really wants those TPS reports.",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_nothing(&mut smtp_rx).await;
|
||||
|
||||
client
|
||||
.vacation_response_set_dates((Utc::now() - Duration::days(1)).timestamp().into(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
smtp_settings.lock().do_stop = true;
|
||||
lmtp.ingest(
|
||||
"jane_smith@example.com",
|
||||
&["jdoe@example.com"],
|
||||
concat!(
|
||||
"From: jane_smith@example.com\r\n",
|
||||
"To: jdoe@example.com\r\n",
|
||||
"Subject: When were you going on holidays?\r\n",
|
||||
"\r\n",
|
||||
"I'm asking because Bill really wants those TPS reports.",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
lmtp.quit().await;
|
||||
|
||||
assert_message_delivery(
|
||||
&mut smtp_rx,
|
||||
MockMessage::new(
|
||||
"<jdoe@example.com>",
|
||||
["<jane_smith@example.com>"],
|
||||
"@Kokomo",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove test data
|
||||
let implement = "true";
|
||||
/*for account_id in [&account_id, &domain_id] {
|
||||
client
|
||||
.set_default_account_id(Id::new(SUPERUSER_ID as u64))
|
||||
.principal_destroy(account_id)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
server.store.principal_purge().unwrap();
|
||||
server.store.assert_is_empty();
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user