Files
Stalwart/tests/src/jmap/sieve_script.rs
2023-05-21 10:50:25 +00:00

479 lines
13 KiB
Rust

use std::{
fs,
path::PathBuf,
sync::Arc,
time::{Duration, Instant},
};
use jmap::JMAP;
use jmap_client::{
client::Client,
core::set::{SetError, SetErrorType},
email, mailbox,
sieve::query::{Comparator, Filter},
Error,
};
use crate::jmap::{
delivery::SmtpConnection,
email_submission::{assert_message_delivery, spawn_mock_smtp_server, MockMessage},
mailbox::destroy_all_mailboxes,
test_account_create,
};
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@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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;
// 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() + Duration::from_secs(10),
);
// Run reject and duplicate check tests
lmtp.ingest(
"bill@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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."
),
)
.await;
assert_eq!(
client
.email_query(None::<email::query::Filter>, None::<Vec<_>>)
.await
.unwrap()
.ids()
.len(),
1,
"Reject failed."
);
assert_message_delivery(
&mut smtp_rx,
MockMessage::new("<>", ["<bill@remote.org>"], "@No soup for you"),
false,
)
.await;
// 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(
"bill@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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."
),
)
.await;
assert_message_delivery(
&mut smtp_rx,
MockMessage::new(
"<>",
["<bill@remote.org>"],
"@Rejected from an included script",
),
false,
)
.await;
// Run enclose + redirect tests
client
.sieve_script_create(
"test_redirect_enclose",
get_script("test_redirect_enclose"),
true,
)
.await
.unwrap();
lmtp.ingest(
"bill@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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@remote.org>"],
"@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@remote.org",
&["jdoe@example.com"],
concat!(
"From: bill@remote.org\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@remote.org>"],
"@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
client.sieve_script_deactivate().await.unwrap();
let mut request = client.build();
request.query_sieve_script();
for id in request.send_query_sieve_script().await.unwrap().take_ids() {
client.sieve_script_destroy(&id).await.unwrap();
}
destroy_all_mailboxes(client).await;
server.store.assert_is_empty().await;
}
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()
}