EmailSubmission tests passing

This commit is contained in:
Mauro D
2023-05-23 13:49:21 +00:00
parent 903f08575a
commit 4ff2158783
43 changed files with 2065 additions and 611 deletions

View File

@@ -3,10 +3,11 @@ use std::{sync::Arc, time::Duration};
use jmap::JMAP;
use jmap_client::{
client::{Client, Credentials},
core::set::{SetError, SetErrorType},
mailbox::{self},
};
use crate::jmap::{mailbox::destroy_all_mailboxes, test_account_create};
use crate::jmap::{mailbox::destroy_all_mailboxes, test_account_create, test_alias_create};
pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
println!("Running Authorization tests...");
@@ -15,18 +16,19 @@ pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
let account_id = test_account_create(&server, "jdoe@example.com", "12345", "John Doe")
.await
.to_string();
test_alias_create(&server, "jdoe@example.com", "john.doe@example.com", false).await;
// Wait for rate limit to be restored after running previous tests
tokio::time::sleep(Duration::from_secs(1)).await;
//tokio::time::sleep(Duration::from_secs(1)).await;
// Incorrect passwords should be rejected with a 401 error
assert!(matches!(
Client::new()
.credentials(Credentials::basic("jdoe@example.com", "abcde"))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(401)));
Client::new()
.credentials(Credentials::basic("jdoe@example.com", "abcde"))
.accept_invalid_certs(true)
.connect("https://127.0.0.1:8899")
.await,
Err(jmap_client::Error::Problem(err)) if err.status() == Some(401)));
// Requests should be rate limited
let mut n_401 = 0;
@@ -44,12 +46,12 @@ pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
if problem.status().unwrap() == 401 {
n_401 += 1;
if n_401 > 100 {
panic!("Rate limiter failed.");
panic!("Rate limiter failed: 429: {n_429}, 401: {n_401}.");
}
} else if problem.status().unwrap() == 429 {
n_429 += 1;
if n_429 > 11 {
panic!("Rate limiter too restrictive.");
panic!("Rate limiter too restrictive: 429: {n_429}, 401: {n_401}.");
}
} else {
panic!("Unexpected error status {}", problem.status().unwrap());
@@ -92,13 +94,12 @@ pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
// Users should be allowed to create identities only
// using email addresses associated to their principal
let implement = "true";
/*client
let iid1 = client
.identity_create("John Doe", "jdoe@example.com")
.await
.unwrap()
.take_id();
client
let iid2 = client
.identity_create("John Doe (secondary)", "john.doe@example.com")
.await
.unwrap()
@@ -111,7 +112,9 @@ pub async fn test(server: Arc<JMAP>, admin_client: &mut Client) {
type_: SetErrorType::InvalidProperties,
..
}))
));*/
));
client.identity_destroy(&iid1).await.unwrap();
client.identity_destroy(&iid2).await.unwrap();
// Concurrent requests check
let client = Arc::new(client);

View File

@@ -1,11 +1,14 @@
use std::{sync::Arc, time::Duration};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use ahash::AHashMap;
use jmap::{JMAP, SUPERUSER_ID};
use jmap::JMAP;
use jmap_client::{
client::Client,
core::set::{SetError, SetErrorType, SetObject},
email_submission::{Address, Delivered, DeliveryStatus, Displayed, UndoStatus},
email_submission::{query::Filter, Address, Delivered, DeliveryStatus, Displayed, UndoStatus},
mailbox::Role,
Error,
};
@@ -18,7 +21,9 @@ use tokio::{
sync::mpsc,
};
use crate::jmap::email_set::assert_email_properties;
use crate::jmap::{
email_set::assert_email_properties, mailbox::destroy_all_mailboxes, test_account_create,
};
#[derive(Default, Debug, PartialEq, Eq)]
pub struct MockMessage {
@@ -49,32 +54,26 @@ pub struct MockSMTPSettings {
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();
server.smtp.resolvers.dns.ipv4_add(
"localhost",
vec!["127.0.0.1".parse().unwrap()],
Instant::now() + std::time::Duration::from_secs(10),
);
// Create a test account
let account_id = test_account_create(&server, "jdoe@example.com", "12345", "John Doe")
.await
.to_string();
// 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")
.set_default_account_id(&account_id)
.identity_create("John Doe", "someaddress@domain.com")
.await
.unwrap_err()
{
@@ -82,20 +81,8 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
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();
// Create an identity
let identity_id = client
.set_default_account_id(&account_id)
.identity_create("John Doe", "jdoe@example.com")
.await
.unwrap()
@@ -151,7 +138,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
.email_submission_create(&email_id, &identity_id)
.await,
Err(Error::Set(SetError {
type_: SetErrorType::InvalidProperties,
type_: SetErrorType::NoRecipients,
..
}))
));
@@ -168,14 +155,14 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
)
.await,
Err(Error::Set(SetError {
type_: SetErrorType::InvalidProperties,
type_: SetErrorType::ForbiddenFrom,
..
}))
));
// 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";
"From: jdoe@example.com\r\nTo: jane_smith@remote.org\r\nSubject: hey\r\n\r\ntest";
let email_id = client
.email_import(
email_body.as_bytes().to_vec(),
@@ -196,10 +183,9 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
&mut smtp_rx,
MockMessage::new(
"<jdoe@example.com>",
["<jane_smith@example.com>"],
["<jane_smith@remote.org>"],
email_body,
),
false,
)
.await;
@@ -221,20 +207,25 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
.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;
for _ in 0..3 {
let mut message = expect_message_delivery(&mut smtp_rx).await;
assert_eq!(message.mail_from, "<jdoe@example.com>");
let rcpt_to = message.rcpt_to.pop().unwrap();
assert!([
"<james@other_domain.com>",
"<secret_rcpt@test.com>",
"<tim@foobar.com>",
]
.contains(&rcpt_to.as_str()));
assert!(
message.message.contains(email_body),
"Got [{}], Expected[{}]",
message.message,
email_body
);
}
// Confirm that the email submission status was updated
tokio::time::sleep(Duration::from_millis(100)).await;
@@ -249,27 +240,31 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
&AHashMap::from_iter([
(
"tim@foobar.com".to_string(),
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, Displayed::Unknown)
),
(
"secret_rcpt@test.com".to_string(),
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, Displayed::Unknown)
),
(
"james@other_domain.com".to_string(),
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, 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"],
[
"nonexistant@example.com",
"delay@other_domain.com",
"fail@test.com",
"tim@foobar.com",
],
)
.await
.unwrap()
@@ -277,61 +272,64 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
assert_message_delivery(
&mut smtp_rx,
MockMessage::new("<jdoe@example.com>", ["<tim@foobar.com>"], email_body),
false,
)
.await;
expect_nothing(&mut smtp_rx).await;
// Confirm that all delivery failures were included
// Verify SMTP replies
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.undo_status().unwrap(),
&UndoStatus::Pending
);
assert_eq!(
email_submission.delivery_status().unwrap(),
&AHashMap::from_iter([
(
"james@other_domain.com".to_string(),
"nonexistant@example.com".to_string(),
DeliveryStatus::new(
"550 I refuse to accept that recipient.",
"550 5.1.2 Mailbox does not exist.",
Delivered::No,
Displayed::Unknown
)
),
(
"jane@test.com".to_string(),
"delay@other_domain.com".to_string(),
DeliveryStatus::new(
"550 I refuse to accept that recipient.",
"Code: 451, Enhanced code: 4.5.3, Message: Try again later.",
Delivered::Queued,
Displayed::Unknown
)
),
(
"fail@test.com".to_string(),
DeliveryStatus::new(
"Code: 550, Enhanced code: 0.0.0, Message: I refuse to accept that recipient.",
Delivered::No,
Displayed::Unknown
)
),
(
"tim@foobar.com".to_string(),
DeliveryStatus::new("250 OK", Delivered::Queued, Displayed::Unknown)
DeliveryStatus::new(
"Code: 250, Enhanced code: 0.0.0, Message: OK",
Delivered::Yes,
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"],
)
// Cancel submission
client
.email_submission_change_status(&email_submission_id, UndoStatus::Canceled)
.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;
.unwrap();
let email_submission = client
.email_submission_get(&email_submission_id, None)
.await
@@ -345,73 +343,59 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
email_submission.delivery_status().unwrap(),
&AHashMap::from_iter([
(
"james@other_domain.com".to_string(),
"nonexistant@example.com".to_string(),
DeliveryStatus::new(
"503 Thank you but I am saving myself for dessert.",
"550 5.1.2 Mailbox does not exist.",
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
)
"delay@other_domain.com".to_string(),
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, Displayed::Unknown)
),
(
"fail@test.com".to_string(),
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, Displayed::Unknown)
),
(
"tim@foobar.com".to_string(),
DeliveryStatus::new(
"503 Thank you but I am saving myself for dessert.",
Delivered::No,
Displayed::Unknown
)
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Unknown, 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 hold_until = DateTime::parse_rfc3339("2079-11-20T05:00:00Z")
.unwrap()
.to_timestamp();
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"],
Address::new("jdoe@example.com").parameter("HOLDUNTIL", Some(hold_until.to_string())),
["jane_smith@remote.org"],
)
.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(), hold_until);
assert_eq!(
email_submission.send_at().unwrap(),
DateTime::parse_rfc3339("2079-11-20T05:00:00Z")
.unwrap()
.to_timestamp()
email_submission.undo_status().unwrap(),
&UndoStatus::Pending
);
assert_eq!(
email_submission.delivery_status().unwrap(),
&AHashMap::from_iter([(
"jane_smith@remote.org".to_string(),
DeliveryStatus::new("250 2.1.5 Queued", Delivered::Queued, Displayed::Unknown)
),])
);
// Verify onSuccessUpdateEmail action
@@ -434,7 +418,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
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
@@ -451,17 +434,20 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
.await
.unwrap()
.is_none());
smtp_settings.lock().do_stop = true;
// Destroy the created mailbox, identity and all submissions
let todo = "true";
/*client
.set_default_account_id(Id::from(SUPERUSER_ID))
.principal_destroy(&account_id)
client.identity_destroy(&identity_id).await.unwrap();
for id in client
.email_submission_query(None::<Filter>, None::<Vec<_>>)
.await
.unwrap();
client.principal_destroy(&domain_id).await.unwrap();
server.store.principal_purge().unwrap();
server.store.assert_is_empty();*/
.unwrap()
.take_ids()
{
client.email_submission_destroy(&id).await.unwrap();
}
destroy_all_mailboxes(client).await;
server.store.assert_is_empty().await;
}
pub fn spawn_mock_smtp_server() -> (mpsc::Receiver<MockMessage>, Arc<Mutex<MockSMTPSettings>>) {
@@ -504,12 +490,16 @@ pub fn spawn_mock_smtp_server() -> (mpsc::Receiver<MockMessage>, Arc<Mutex<MockS
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") {
if buf.contains("fail@") {
tx.write_all(
"550-I refuse to\r\n550 accept that recipient.\r\n".as_bytes(),
)
.await
.unwrap();
} else if buf.contains("delay@") {
tx.write_all("451 4.5.3 Try again later.\r\n".as_bytes())
.await
.unwrap();
} else {
message
.rcpt_to
@@ -573,51 +563,45 @@ pub fn spawn_mock_smtp_server() -> (mpsc::Receiver<MockMessage>, Arc<Mutex<MockS
(event_rx, _settings)
}
pub async fn assert_message_delivery(
event_rx: &mut mpsc::Receiver<MockMessage>,
expected_message: MockMessage,
expect_dkim: bool,
) {
pub async fn expect_message_delivery(event_rx: &mut mpsc::Receiver<MockMessage>) -> MockMessage {
match tokio::time::timeout(Duration::from_millis(3000), event_rx.recv()).await {
Ok(Some(message)) => {
println!("Got message [{}]", message.message);
assert_eq!(message.mail_from, expected_message.mail_from);
assert_eq!(message.rcpt_to, expected_message.rcpt_to);
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);
}
message
}
result => {
panic!(
"Timeout waiting for message {:?}: {:?}",
expected_message, result
);
panic!("Timeout waiting for message, got: {:?}", result);
}
}
}
pub async fn assert_message_delivery(
event_rx: &mut mpsc::Receiver<MockMessage>,
expected_message: MockMessage,
) {
let message = expect_message_delivery(event_rx).await;
assert_eq!(message.mail_from, expected_message.mail_from);
assert_eq!(message.rcpt_to, expected_message.rcpt_to);
if let Some(needle) = expected_message.message.strip_prefix('@') {
assert!(
message.message.contains(needle),
"[{}] needle = {:?}",
message.message,
needle
);
} else {
assert!(
message.message.contains(&expected_message.message),
"Got [{}], Expected[{}]",
message.message,
expected_message.message
);
}
}
pub async fn expect_nothing(event_rx: &mut mpsc::Receiver<MockMessage>) {
match tokio::time::timeout(Duration::from_millis(500), event_rx.recv()).await {
Err(_) => {}

View File

@@ -57,6 +57,10 @@ certificate = "default"
[session.ehlo]
reject-non-fqdn = false
[session.rcpt]
relay = [ { if = "authenticated-as", ne = "", then = true },
{ else = false } ]
[session.rcpt.lookup]
domains = "list/domains"
addresses = "local"
@@ -69,6 +73,7 @@ wait = "1ms"
[list]
domains = ["example.com"]
remote-domains = ["remote.org", "foobar.com", "test.com", "other_domain.com"]
[queue]
path = "{TMP}"
@@ -83,7 +88,7 @@ type = "system"
[queue.outbound]
next-hop = [ { if = "rcpt-domain", in-list = "list/domains", then = "local" },
{ if = "rcpt-domain", eq = "remote.org", then = "mock-smtp" },
{ if = "rcpt-domain", in-list = "list/remote-domains", then = "mock-smtp" },
{ else = false } ]
[remote."mock-smtp"]
@@ -95,6 +100,10 @@ protocol = "smtp"
implicit = false
allow-invalid-certs = true
[session.extensions]
future-release = [ { if = "authenticated-as", ne = "", then = "99999999d"},
{ else = false } ]
[store]
db.path = "{TMP}/sqlite.db"
blob.path = "{TMP}"
@@ -114,9 +123,9 @@ max-size = 5000000
max-concurrent = 4
[jmap.rate-limit]
account.rate = "100/1m"
account.rate = "1000/1m"
authentication.rate = "100/1m"
anonymous.rate = "1000/1m"
anonymous.rate = "100/1m"
[jmap.event-source]
throttle = "500ms"
@@ -183,7 +192,8 @@ pub async fn jmap_tests() {
//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;
vacation_response::test(params.server.clone(), &mut params.client).await;
//vacation_response::test(params.server.clone(), &mut params.client).await;
email_submission::test(params.server.clone(), &mut params.client).await;
let websockets = "todo";

View File

@@ -291,7 +291,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
assert_message_delivery(
&mut smtp_rx,
MockMessage::new("<>", ["<bill@remote.org>"], "@No soup for you"),
false,
)
.await;
@@ -326,7 +325,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
["<bill@remote.org>"],
"@Rejected from an included script",
),
false,
)
.await;
@@ -359,7 +357,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
["<jane@remote.org>"],
"@Attached you'll find",
),
false,
)
.await;
assert_eq!(
@@ -399,7 +396,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
["<sms_gateway@remote.org>"],
"@It's TPS-o-clock",
),
false,
)
.await;

View File

@@ -63,7 +63,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
assert_message_delivery(
&mut smtp_rx,
MockMessage::new("<jdoe@example.com>", ["<bill@remote.org>"], "@Kokomo"),
false,
)
.await;
@@ -143,7 +142,6 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
assert_message_delivery(
&mut smtp_rx,
MockMessage::new("<jdoe@example.com>", ["<jane_smith@remote.org>"], "@Kokomo"),
false,
)
.await;