Add mailpot-tests crate to reuse test code
parent
503e214801
commit
b48a3c9d12
|
@ -1570,7 +1570,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"error-chain",
|
||||
"log",
|
||||
"mailin-embedded",
|
||||
"mailpot-tests",
|
||||
"melib",
|
||||
"minijinja",
|
||||
"reqwest",
|
||||
|
@ -1592,6 +1592,7 @@ dependencies = [
|
|||
"clap_mangen",
|
||||
"log",
|
||||
"mailpot",
|
||||
"mailpot-tests",
|
||||
"predicates",
|
||||
"stderrlog",
|
||||
"tempfile",
|
||||
|
@ -1606,6 +1607,19 @@ dependencies = [
|
|||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mailpot-tests"
|
||||
version = "0.0.0+2023-04-21"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"log",
|
||||
"mailin-embedded",
|
||||
"mailpot",
|
||||
"predicates",
|
||||
"stderrlog",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mailpot-web"
|
||||
version = "0.0.0+2023-04-21"
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"archive-http",
|
||||
"cli",
|
||||
"core",
|
||||
"mailpot-tests",
|
||||
"rest-http",
|
||||
"web",
|
||||
]
|
||||
|
|
|
@ -23,6 +23,7 @@ stderrlog = "^0.5"
|
|||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
mailpot-tests = { version = "^0.0", path = "../mailpot-tests" }
|
||||
predicates = "3"
|
||||
tempfile = "3.3"
|
||||
|
||||
|
|
|
@ -24,9 +24,31 @@ use mailpot::{
|
|||
models::{changesets::ListSubscriptionChangeset, *},
|
||||
Configuration, Connection, Queue, SendMail,
|
||||
};
|
||||
use mailpot_tests::*;
|
||||
use predicates::prelude::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn generate_mail(from: &str, to: &str, subject: &str, body: &str, seq: &mut usize) -> String {
|
||||
format!(
|
||||
"From: {from}@example.com
|
||||
To: <foo-chat{to}@example.com>
|
||||
Subject: {subject}
|
||||
Date: Thu, 29 Oct 2020 13:58:16 +0000
|
||||
Message-ID:
|
||||
<aaa{}@example.com>
|
||||
Content-Language: en-US
|
||||
Content-Type: text/plain
|
||||
|
||||
{body}
|
||||
",
|
||||
{
|
||||
let val = *seq;
|
||||
*seq += 1;
|
||||
val
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_out_queue_flush() {
|
||||
use assert_cmd::Command;
|
||||
|
@ -35,9 +57,9 @@ fn test_out_queue_flush() {
|
|||
|
||||
let conf_path = tmp_dir.path().join("conf.toml");
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
|
||||
let smtp_handler = TestSmtpHandler::builder().address("127.0.0.1:8826").build();
|
||||
let config = Configuration {
|
||||
send_mail: SendMail::ShellCommand("/usr/bin/true".to_string()),
|
||||
send_mail: SendMail::Smtp(smtp_handler.smtp_conf()),
|
||||
db_path,
|
||||
data_path: tmp_dir.path().to_path_buf(),
|
||||
administrators: vec![],
|
||||
|
@ -47,6 +69,8 @@ fn test_out_queue_flush() {
|
|||
|
||||
std::fs::write(&conf_path, config_str.as_bytes()).unwrap();
|
||||
|
||||
log::info!("Creating foo-chat@example.com mailing list.");
|
||||
let post_policy;
|
||||
let foo_chat = {
|
||||
let db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
|
@ -64,7 +88,7 @@ fn test_out_queue_flush() {
|
|||
.unwrap();
|
||||
|
||||
assert_eq!(foo_chat.pk(), 1);
|
||||
let _post_policy = db
|
||||
post_policy = db
|
||||
.set_list_post_policy(PostPolicy {
|
||||
pk: -1,
|
||||
list: foo_chat.pk(),
|
||||
|
@ -78,6 +102,19 @@ fn test_out_queue_flush() {
|
|||
foo_chat
|
||||
};
|
||||
|
||||
let headers_fn = |env: &melib::Envelope| {
|
||||
assert!(env.subject().starts_with(&format!("[{}] ", foo_chat.id)));
|
||||
let headers = env.other_headers();
|
||||
|
||||
assert_eq!(headers.get("List-Id"), Some(&foo_chat.id_header()));
|
||||
assert_eq!(headers.get("List-Help"), foo_chat.help_header().as_ref());
|
||||
assert_eq!(
|
||||
headers.get("List-Post"),
|
||||
foo_chat.post_header(Some(&post_policy)).as_ref()
|
||||
);
|
||||
};
|
||||
|
||||
log::info!("Running mpot flush-queue on empty out queue.");
|
||||
let mut cmd = Command::cargo_bin("mpot").unwrap();
|
||||
let output = cmd
|
||||
.arg("-vv")
|
||||
|
@ -93,33 +130,14 @@ fn test_out_queue_flush() {
|
|||
.normalize(),
|
||||
);
|
||||
|
||||
fn generate_mail(from: &str, to: &str, subject: &str, body: &str, seq: &mut usize) -> String {
|
||||
format!(
|
||||
"From: {from}@example.com
|
||||
To: <foo-chat{to}@example.com>
|
||||
Subject: {subject}
|
||||
Date: Thu, 29 Oct 2020 13:58:16 +0000
|
||||
Message-ID:
|
||||
<aaa{}@example.com>
|
||||
Content-Language: en-US
|
||||
Content-Type: text/plain
|
||||
|
||||
{body}
|
||||
",
|
||||
{
|
||||
let val = *seq;
|
||||
*seq += 1;
|
||||
val
|
||||
}
|
||||
)
|
||||
}
|
||||
let mut seq = 0; // for generated emails
|
||||
log::info!("Subscribe two users, Αλίκη and Χαραλάμπης to foo-chat.");
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
.trusted();
|
||||
|
||||
let mut seq = 0;
|
||||
for who in ["Αλίκη", "Χαραλάμπης"] {
|
||||
// = ["Alice", "Bob"]
|
||||
let mail = generate_mail(who, "+request", "subscribe", "", &mut seq);
|
||||
|
@ -139,16 +157,160 @@ Content-Type: text/plain
|
|||
assert_eq!(out_queue.len(), 2);
|
||||
assert_eq!(db.list_subscriptions(foo_chat.pk()).unwrap().len(), 2);
|
||||
assert_eq!(db.error_queue().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
log::info!("Flush out queue, subscription confirmations should be sent to the new users.");
|
||||
let mut cmd = Command::cargo_bin("mpot").unwrap();
|
||||
let output = cmd
|
||||
.arg("-vv")
|
||||
.arg("-c")
|
||||
.arg(&conf_path)
|
||||
.arg("flush-queue")
|
||||
.output()
|
||||
.unwrap()
|
||||
.assert();
|
||||
output.code(0).stdout(
|
||||
predicate::eq("Queue out has 2 messages.")
|
||||
.trim()
|
||||
.normalize(),
|
||||
);
|
||||
|
||||
/* Check that confirmation emails are correct */
|
||||
let stored = std::mem::take(&mut *smtp_handler.stored.lock().unwrap());
|
||||
assert_eq!(stored.len(), 2);
|
||||
assert_eq!(stored[0].0, "=?UTF-8?B?zpHOu86vzrrOtw==?=@example.com");
|
||||
assert_eq!(
|
||||
stored[1].0,
|
||||
"=?UTF-8?B?zqfOsc+BzrHOu86szrzPgM63z4I=?=@example.com"
|
||||
);
|
||||
for item in stored.iter() {
|
||||
assert_eq!(
|
||||
item.1.subject(),
|
||||
"[foo-chat] You have successfully subscribed to foobar chat."
|
||||
);
|
||||
assert_eq!(
|
||||
&item.1.field_from_to_string(),
|
||||
"foo-chat+request@example.com"
|
||||
);
|
||||
headers_fn(&item.1);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Χαραλάμπης submits a post to list. Flush out queue, Χαραλάμπης' post should be relayed \
|
||||
to Αλίκη, and Χαραλάμπης should receive a copy of their own post because of \
|
||||
`receive_own_posts` setting."
|
||||
);
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
.trusted();
|
||||
let mail = generate_mail("Χαραλάμπης", "", "hello world", "Hello there.", &mut seq);
|
||||
let subenvelope = mailpot::melib::Envelope::from_bytes(mail.as_bytes(), None)
|
||||
.expect("Could not parse message");
|
||||
db.post(&subenvelope, mail.as_bytes(), /* dry_run */ false)
|
||||
.unwrap();
|
||||
let out_queue = db.queue(Queue::Out).unwrap();
|
||||
assert_eq!(out_queue.len(), 4);
|
||||
assert_eq!(out_queue.len(), 2);
|
||||
}
|
||||
|
||||
// [ref:TODO] hook smtp dev server.
|
||||
let mut cmd = Command::cargo_bin("mpot").unwrap();
|
||||
let output = cmd
|
||||
.arg("-vv")
|
||||
.arg("-c")
|
||||
.arg(&conf_path)
|
||||
.arg("flush-queue")
|
||||
.output()
|
||||
.unwrap()
|
||||
.assert();
|
||||
output.code(0).stdout(
|
||||
predicate::eq("Queue out has 2 messages.")
|
||||
.trim()
|
||||
.normalize(),
|
||||
);
|
||||
|
||||
/* Check that user posts are correct */
|
||||
{
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
|
||||
let out_queue = db.queue(Queue::Out).unwrap();
|
||||
assert_eq!(out_queue.len(), 0);
|
||||
}
|
||||
|
||||
let stored = std::mem::take(&mut *smtp_handler.stored.lock().unwrap());
|
||||
assert_eq!(stored.len(), 2);
|
||||
assert_eq!(stored[0].0, "Αλίκη@example.com");
|
||||
assert_eq!(stored[1].0, "Χαραλάμπης@example.com");
|
||||
assert_eq!(stored[0].1.message_id(), stored[1].1.message_id());
|
||||
assert_eq!(stored[0].1.other_headers(), stored[1].1.other_headers());
|
||||
headers_fn(&stored[0].1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_requests_submission() {
|
||||
use assert_cmd::Command;
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let conf_path = tmp_dir.path().join("conf.toml");
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
let smtp_handler = TestSmtpHandler::builder().address("127.0.0.1:8827").build();
|
||||
let config = Configuration {
|
||||
send_mail: SendMail::Smtp(smtp_handler.smtp_conf()),
|
||||
db_path,
|
||||
data_path: tmp_dir.path().to_path_buf(),
|
||||
administrators: vec![],
|
||||
};
|
||||
|
||||
let config_str = config.to_toml();
|
||||
|
||||
std::fs::write(&conf_path, config_str.as_bytes()).unwrap();
|
||||
|
||||
log::info!("Creating foo-chat@example.com mailing list.");
|
||||
let post_policy;
|
||||
let foo_chat = {
|
||||
let db = Connection::open_or_create_db(config.clone())
|
||||
.unwrap()
|
||||
.trusted();
|
||||
|
||||
let foo_chat = db
|
||||
.create_list(MailingList {
|
||||
pk: 0,
|
||||
name: "foobar chat".into(),
|
||||
id: "foo-chat".into(),
|
||||
address: "foo-chat@example.com".into(),
|
||||
description: None,
|
||||
archive_url: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(foo_chat.pk(), 1);
|
||||
post_policy = db
|
||||
.set_list_post_policy(PostPolicy {
|
||||
pk: -1,
|
||||
list: foo_chat.pk(),
|
||||
announce_only: false,
|
||||
subscription_only: false,
|
||||
approval_needed: false,
|
||||
open: true,
|
||||
custom: false,
|
||||
})
|
||||
.unwrap();
|
||||
foo_chat
|
||||
};
|
||||
|
||||
let headers_fn = |env: &melib::Envelope| {
|
||||
let headers = env.other_headers();
|
||||
|
||||
assert_eq!(headers.get("List-Id"), Some(&foo_chat.id_header()));
|
||||
assert_eq!(headers.get("List-Help"), foo_chat.help_header().as_ref());
|
||||
assert_eq!(
|
||||
headers.get("List-Post"),
|
||||
foo_chat.post_header(Some(&post_policy)).as_ref()
|
||||
);
|
||||
};
|
||||
|
||||
log::info!("Running mpot flush-queue on empty out queue.");
|
||||
let mut cmd = Command::cargo_bin("mpot").unwrap();
|
||||
let output = cmd
|
||||
.arg("-vv")
|
||||
|
@ -159,15 +321,52 @@ Content-Type: text/plain
|
|||
.unwrap()
|
||||
.assert();
|
||||
output.code(0).stderr(predicates::str::is_empty()).stdout(
|
||||
predicate::eq("Queue out has 4 messages.")
|
||||
predicate::eq("Queue out has 0 messages.")
|
||||
.trim()
|
||||
.normalize(),
|
||||
);
|
||||
|
||||
{
|
||||
let db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
let mut seq = 0; // for generated emails
|
||||
log::info!("User Αλίκη sends to foo-chat+request with subject 'help'.");
|
||||
|
||||
{
|
||||
let mut db = Connection::open_or_create_db(config).unwrap().trusted();
|
||||
|
||||
let mail = generate_mail("Αλίκη", "+request", "help", "", &mut seq);
|
||||
let subenvelope = mailpot::melib::Envelope::from_bytes(mail.as_bytes(), None)
|
||||
.expect("Could not parse message");
|
||||
db.post(&subenvelope, mail.as_bytes(), /* dry_run */ false)
|
||||
.unwrap();
|
||||
let out_queue = db.queue(Queue::Out).unwrap();
|
||||
assert_eq!(out_queue.len(), 0);
|
||||
assert_eq!(out_queue.len(), 1);
|
||||
assert_eq!(db.list_subscriptions(foo_chat.pk()).unwrap().len(), 0);
|
||||
assert_eq!(db.error_queue().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
log::info!("Flush out queue, help reply should go to Αλίκη.");
|
||||
let mut cmd = Command::cargo_bin("mpot").unwrap();
|
||||
let output = cmd
|
||||
.arg("-vv")
|
||||
.arg("-c")
|
||||
.arg(&conf_path)
|
||||
.arg("flush-queue")
|
||||
.output()
|
||||
.unwrap()
|
||||
.assert();
|
||||
output.code(0).stdout(
|
||||
predicate::eq("Queue out has 1 messages.")
|
||||
.trim()
|
||||
.normalize(),
|
||||
);
|
||||
|
||||
/* Check that help email is correct */
|
||||
let stored = std::mem::take(&mut *smtp_handler.stored.lock().unwrap());
|
||||
assert_eq!(stored.len(), 1);
|
||||
assert_eq!(stored[0].0, "=?UTF-8?B?zpHOu86vzrrOtw==?=@example.com");
|
||||
assert_eq!(stored[0].1.subject(), "Help for foobar chat");
|
||||
assert_eq!(
|
||||
&stored[0].1.field_from_to_string(),
|
||||
"foo-chat+request@example.com"
|
||||
);
|
||||
headers_fn(&stored[0].1);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ toml = "^0.5"
|
|||
xdg = "2.4.1"
|
||||
|
||||
[dev-dependencies]
|
||||
mailin-embedded = { version = "0.7", features = ["rtls"] }
|
||||
mailpot-tests = { version = "^0.0", path = "../mailpot-tests" }
|
||||
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking"] }
|
||||
stderrlog = "^0.5"
|
||||
tempfile = "3.3"
|
||||
|
|
|
@ -286,7 +286,6 @@ impl Connection {
|
|||
let post_policy = self.list_post_policy(list.pk)?;
|
||||
match request {
|
||||
ListRequest::Help => {
|
||||
// [ref:TODO] add test for this
|
||||
trace!(
|
||||
"help action for addresses {:?} in list {}",
|
||||
env.from(),
|
||||
|
|
|
@ -153,9 +153,12 @@ impl<S: AsRef<str>> TryFrom<(S, &melib::Envelope)> for ListRequest {
|
|||
fn try_from((val, env): (S, &melib::Envelope)) -> std::result::Result<Self, Self::Error> {
|
||||
let val = val.as_ref();
|
||||
Ok(match val {
|
||||
"subscribe" | "request" if env.subject().trim() == "subscribe" => Self::Subscribe,
|
||||
"unsubscribe" | "request" if env.subject().trim() == "unsubscribe" => Self::Unsubscribe,
|
||||
"subscribe" => Self::Subscribe,
|
||||
"request" if env.subject().trim() == "subscribe" => Self::Subscribe,
|
||||
"unsubscribe" => Self::Unsubscribe,
|
||||
"request" if env.subject().trim() == "unsubscribe" => Self::Unsubscribe,
|
||||
"help" => Self::Help,
|
||||
"request" if env.subject().trim() == "help" => Self::Help,
|
||||
"request" => Self::Other(env.subject().trim().to_string()),
|
||||
_ => {
|
||||
// [ref:TODO] add ChangeSetting parsing
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_accounts() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
|
||||
const SSH_KEY: &[u8] = include_bytes!("./ssh_key.pub");
|
||||
|
||||
|
|
|
@ -17,16 +17,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_authorizer() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_init_empty() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
|
@ -42,7 +41,7 @@ fn test_init_empty() {
|
|||
|
||||
#[test]
|
||||
fn test_list_creation() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use mailpot::{melib, models::*, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn get_smtp_conf() -> melib::smtp::SmtpServerConf {
|
||||
|
@ -36,7 +35,7 @@ fn get_smtp_conf() -> melib::smtp::SmtpServerConf {
|
|||
|
||||
#[test]
|
||||
fn test_error_queue() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
|
|
|
@ -17,200 +17,23 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use log::{trace, warn};
|
||||
use mailin_embedded::{Handler, Response, Server, SslConfig};
|
||||
use mailpot::{melib, models::*, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot::{melib, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot_tests::*;
|
||||
use melib::smol;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const ADDRESS: &str = "127.0.0.1:8825";
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Helo,
|
||||
Mail {
|
||||
from: String,
|
||||
},
|
||||
Rcpt {
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
},
|
||||
DataStart {
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
},
|
||||
Data {
|
||||
#[allow(dead_code)]
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
buf: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct MyHandler {
|
||||
mails: Arc<Mutex<Vec<((IpAddr, String), Message)>>>,
|
||||
stored: Arc<Mutex<Vec<(String, melib::Envelope)>>>,
|
||||
}
|
||||
use mailin_embedded::response::{INTERNAL_ERROR, OK};
|
||||
|
||||
impl Handler for MyHandler {
|
||||
fn helo(&mut self, ip: IpAddr, domain: &str) -> Response {
|
||||
// eprintln!("helo ip {:?} domain {:?}", ip, domain);
|
||||
self.mails
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(((ip, domain.to_string()), Message::Helo));
|
||||
OK
|
||||
}
|
||||
|
||||
fn mail(&mut self, ip: IpAddr, domain: &str, from: &str) -> Response {
|
||||
// eprintln!("mail() ip {:?} domain {:?} from {:?}", ip, domain, from);
|
||||
if let Some((_, message)) = self
|
||||
.mails
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.find(|((i, d), _)| (i, d.as_str()) == (&ip, domain))
|
||||
{
|
||||
if let Message::Helo = message {
|
||||
*message = Message::Mail {
|
||||
from: from.to_string(),
|
||||
};
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn rcpt(&mut self, _to: &str) -> Response {
|
||||
// eprintln!("rcpt() to {:?}", _to);
|
||||
if let Some((_, message)) = self.mails.lock().unwrap().last_mut() {
|
||||
if let Message::Mail { from } = message {
|
||||
*message = Message::Rcpt {
|
||||
from: from.clone(),
|
||||
to: vec![_to.to_string()],
|
||||
};
|
||||
return OK;
|
||||
} else if let Message::Rcpt { to, .. } = message {
|
||||
to.push(_to.to_string());
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn data_start(
|
||||
&mut self,
|
||||
_domain: &str,
|
||||
_from: &str,
|
||||
_is8bit: bool,
|
||||
_to: &[String],
|
||||
) -> Response {
|
||||
// eprintln!( "data_start() domain {:?} from {:?} is8bit {:?} to {:?}", _domain,
|
||||
// _from, _is8bit, _to);
|
||||
if let Some(((_, d), ref mut message)) = self.mails.lock().unwrap().last_mut() {
|
||||
if d != _domain {
|
||||
return INTERNAL_ERROR;
|
||||
}
|
||||
if let Message::Rcpt { from, to } = message {
|
||||
*message = Message::DataStart {
|
||||
from: from.to_string(),
|
||||
to: to.to_vec(),
|
||||
};
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn data(&mut self, _buf: &[u8]) -> Result<(), std::io::Error> {
|
||||
if let Some(((_, _), ref mut message)) = self.mails.lock().unwrap().last_mut() {
|
||||
if let Message::DataStart { from, to } = message {
|
||||
*message = Message::Data {
|
||||
from: from.to_string(),
|
||||
to: to.clone(),
|
||||
buf: _buf.to_vec(),
|
||||
};
|
||||
return Ok(());
|
||||
} else if let Message::Data { buf, .. } = message {
|
||||
buf.extend(_buf.iter());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data_end(&mut self) -> Response {
|
||||
let last = self.mails.lock().unwrap().pop();
|
||||
if let Some(((ip, domain), Message::Data { from: _, to, buf })) = last {
|
||||
for to in to {
|
||||
match melib::Envelope::from_bytes(&buf, None) {
|
||||
Ok(env) => {
|
||||
self.stored.lock().unwrap().push((to.clone(), env));
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("envelope parse error {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mails
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(((ip, domain), Message::Helo));
|
||||
return OK;
|
||||
}
|
||||
panic!("last self.mails item was not Message::Data: {last:?}"); //INTERNAL_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
fn get_smtp_conf() -> melib::smtp::SmtpServerConf {
|
||||
use melib::smtp::*;
|
||||
SmtpServerConf {
|
||||
hostname: "127.0.0.1".into(),
|
||||
port: 8825,
|
||||
envelope_from: "foo-chat@example.com".into(),
|
||||
auth: SmtpAuth::None,
|
||||
security: SmtpSecurity::None,
|
||||
extensions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smtp() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
let handler = MyHandler {
|
||||
mails: Arc::new(Mutex::new(vec![])),
|
||||
stored: Arc::new(Mutex::new(vec![])),
|
||||
};
|
||||
let handler2 = handler.clone();
|
||||
let _smtp_handle = thread::spawn(move || {
|
||||
let mut server = Server::new(handler2);
|
||||
|
||||
server
|
||||
.with_name("example.com")
|
||||
.with_ssl(SslConfig::None)
|
||||
.unwrap()
|
||||
.with_addr(ADDRESS)
|
||||
.unwrap();
|
||||
eprintln!("Running smtp server at {}", ADDRESS);
|
||||
server.serve().expect("Could not run server");
|
||||
});
|
||||
let smtp_handler = TestSmtpHandler::builder().address("127.0.0.1:8825").build();
|
||||
|
||||
let db_path = tmp_dir.path().join("mpot.db");
|
||||
let config = Configuration {
|
||||
send_mail: SendMail::Smtp(get_smtp_conf()),
|
||||
send_mail: SendMail::Smtp(smtp_handler.smtp_conf()),
|
||||
db_path,
|
||||
data_path: tmp_dir.path().to_path_buf(),
|
||||
administrators: vec![],
|
||||
|
@ -313,7 +136,7 @@ fn test_smtp() {
|
|||
.unwrap();
|
||||
}
|
||||
}));
|
||||
let stored = handler.stored.lock().unwrap();
|
||||
let stored = smtp_handler.stored.lock().unwrap();
|
||||
assert_eq!(stored.len(), 3);
|
||||
assert_eq!(&stored[0].0, "japoeunp@example.com");
|
||||
assert_eq!(
|
||||
|
@ -335,7 +158,7 @@ fn test_smtp() {
|
|||
#[test]
|
||||
fn test_smtp_mailcrab() {
|
||||
use std::env;
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
|
||||
fn get_smtp_conf() -> melib::smtp::SmtpServerConf {
|
||||
use melib::smtp::*;
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_list_subscription() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, Queue, SendMail, Template};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_template_replies() {
|
||||
utils::init_stderr_logging();
|
||||
init_stderr_logging();
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* This file is part of mailpot
|
||||
*
|
||||
* Copyright 2020 - Manos Pitsidianakis
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
static INIT_STDERR_LOGGING: Once = Once::new();
|
||||
|
||||
pub fn init_stderr_logging() {
|
||||
INIT_STDERR_LOGGING.call_once(|| {
|
||||
stderrlog::new()
|
||||
.quiet(false)
|
||||
.verbosity(15)
|
||||
.show_module_names(true)
|
||||
.timestamp(stderrlog::Timestamp::Millisecond)
|
||||
.init()
|
||||
.unwrap();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "mailpot-tests"
|
||||
version = "0.0.0+2023-04-21"
|
||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||
edition = "2021"
|
||||
license = "LICENSE"
|
||||
readme = "README.md"
|
||||
description = "test library for mailpot crates"
|
||||
repository = "https://github.com/meli/mailpot"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
assert_cmd = "2"
|
||||
log = "0.4"
|
||||
mailin-embedded = { version = "0.7", features = ["rtls"] }
|
||||
mailpot = { version = "^0.0", path = "../core" }
|
||||
predicates = "3"
|
||||
stderrlog = "^0.5"
|
||||
tempfile = "3.3"
|
||||
|
||||
[dev-dependencies]
|
|
@ -0,0 +1,3 @@
|
|||
# mailpot-tests
|
||||
|
||||
Re-usable testing code for all mailpot crates.
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* This file is part of mailpot
|
||||
*
|
||||
* Copyright 2020 - Manos Pitsidianakis
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::ToSocketAddrs,
|
||||
sync::{Arc, Mutex, Once},
|
||||
thread,
|
||||
};
|
||||
|
||||
pub use assert_cmd;
|
||||
pub use log::{trace, warn};
|
||||
use mailin_embedded::{
|
||||
response::{INTERNAL_ERROR, OK},
|
||||
Handler, Response, Server,
|
||||
};
|
||||
pub use mailpot::{
|
||||
melib::{self, smol, smtp::SmtpServerConf},
|
||||
models::{changesets::ListSubscriptionChangeset, *},
|
||||
Configuration, Connection, Queue, SendMail,
|
||||
};
|
||||
pub use predicates;
|
||||
pub use tempfile::{self, TempDir};
|
||||
|
||||
static INIT_STDERR_LOGGING: Once = Once::new();
|
||||
|
||||
pub fn init_stderr_logging() {
|
||||
INIT_STDERR_LOGGING.call_once(|| {
|
||||
stderrlog::new()
|
||||
.quiet(false)
|
||||
.verbosity(15)
|
||||
.show_module_names(true)
|
||||
.timestamp(stderrlog::Timestamp::Millisecond)
|
||||
.init()
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
pub const ADDRESS: &str = "127.0.0.1:8825";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
Helo,
|
||||
Mail {
|
||||
from: String,
|
||||
},
|
||||
Rcpt {
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
},
|
||||
DataStart {
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
},
|
||||
Data {
|
||||
#[allow(dead_code)]
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
buf: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestSmtpHandler {
|
||||
address: Cow<'static, str>,
|
||||
ssl: SslConfig,
|
||||
envelope_from: Cow<'static, str>,
|
||||
auth: melib::smtp::SmtpAuth,
|
||||
pub messages: Arc<Mutex<Vec<((IpAddr, String), Message)>>>,
|
||||
pub stored: Arc<Mutex<Vec<(String, melib::Envelope)>>>,
|
||||
}
|
||||
|
||||
impl Handler for TestSmtpHandler {
|
||||
fn helo(&mut self, ip: IpAddr, domain: &str) -> Response {
|
||||
//eprintln!("helo ip {:?} domain {:?}", ip, domain);
|
||||
self.messages
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(((ip, domain.to_string()), Message::Helo));
|
||||
OK
|
||||
}
|
||||
|
||||
fn mail(&mut self, ip: IpAddr, domain: &str, from: &str) -> Response {
|
||||
//eprintln!("mail() ip {:?} domain {:?} from {:?}", ip, domain, from);
|
||||
if let Some((_, message)) = self
|
||||
.messages
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|((i, d), _)| (i, d.as_str()) == (&ip, domain))
|
||||
{
|
||||
if let Message::Helo = &message {
|
||||
*message = Message::Mail {
|
||||
from: from.to_string(),
|
||||
};
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn rcpt(&mut self, _to: &str) -> Response {
|
||||
//eprintln!("rcpt() to {:?}", _to);
|
||||
if let Some((_, message)) = self.messages.lock().unwrap().last_mut() {
|
||||
if let Message::Mail { from } = message {
|
||||
*message = Message::Rcpt {
|
||||
from: from.clone(),
|
||||
to: vec![_to.to_string()],
|
||||
};
|
||||
return OK;
|
||||
} else if let Message::Rcpt { to, .. } = message {
|
||||
to.push(_to.to_string());
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn data_start(
|
||||
&mut self,
|
||||
_domain: &str,
|
||||
_from: &str,
|
||||
_is8bit: bool,
|
||||
_to: &[String],
|
||||
) -> Response {
|
||||
// eprintln!( "data_start() domain {:?} from {:?} is8bit {:?} to {:?}", _domain,
|
||||
// _from, _is8bit, _to);
|
||||
if let Some(((_, d), ref mut message)) = self.messages.lock().unwrap().last_mut() {
|
||||
if d != _domain {
|
||||
return INTERNAL_ERROR;
|
||||
}
|
||||
if let Message::Rcpt { from, to } = message {
|
||||
*message = Message::DataStart {
|
||||
from: from.to_string(),
|
||||
to: to.to_vec(),
|
||||
};
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
|
||||
fn data(&mut self, _buf: &[u8]) -> Result<(), std::io::Error> {
|
||||
if let Some(((_, _), ref mut message)) = self.messages.lock().unwrap().last_mut() {
|
||||
if let Message::DataStart { from, to } = message {
|
||||
*message = Message::Data {
|
||||
from: from.to_string(),
|
||||
to: to.clone(),
|
||||
buf: _buf.to_vec(),
|
||||
};
|
||||
return Ok(());
|
||||
} else if let Message::Data { buf, .. } = message {
|
||||
buf.extend(_buf.iter());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data_end(&mut self) -> Response {
|
||||
let last = self.messages.lock().unwrap().pop();
|
||||
if let Some(((ip, domain), Message::Data { from: _, to, buf })) = last {
|
||||
for to in to {
|
||||
match melib::Envelope::from_bytes(&buf, None) {
|
||||
Ok(env) => {
|
||||
self.stored.lock().unwrap().push((to.clone(), env));
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("envelope parse error {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.messages
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(((ip, domain), Message::Helo));
|
||||
return OK;
|
||||
}
|
||||
panic!("last self.messages item was not Message::Data: {last:?}"); //INTERNAL_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSmtpHandler {
|
||||
#[inline]
|
||||
pub fn smtp_conf(&self) -> melib::smtp::SmtpServerConf {
|
||||
use melib::smtp::*;
|
||||
let sockaddr = self
|
||||
.address
|
||||
.as_ref()
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
let ip = sockaddr.ip();
|
||||
let port = sockaddr.port();
|
||||
|
||||
SmtpServerConf {
|
||||
hostname: ip.to_string(),
|
||||
port,
|
||||
envelope_from: self.envelope_from.to_string(),
|
||||
auth: self.auth.clone(),
|
||||
security: SmtpSecurity::None,
|
||||
extensions: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSmtpHandler {
|
||||
pub fn builder() -> TestSmtpHandlerBuilder {
|
||||
TestSmtpHandlerBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSmtpHandlerBuilder {
|
||||
address: Cow<'static, str>,
|
||||
ssl: SslConfig,
|
||||
auth: melib::smtp::SmtpAuth,
|
||||
envelope_from: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl TestSmtpHandlerBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
address: ADDRESS.into(),
|
||||
ssl: SslConfig::None,
|
||||
auth: melib::smtp::SmtpAuth::None,
|
||||
envelope_from: "foo-chat@example.com".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(self, address: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
address: address.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ssl(self, ssl: SslConfig) -> Self {
|
||||
Self { ssl, ..self }
|
||||
}
|
||||
|
||||
pub fn build(self) -> TestSmtpHandler {
|
||||
let Self {
|
||||
address,
|
||||
ssl,
|
||||
auth,
|
||||
envelope_from,
|
||||
} = self;
|
||||
let handler = TestSmtpHandler {
|
||||
address,
|
||||
ssl,
|
||||
auth,
|
||||
envelope_from,
|
||||
messages: Arc::new(Mutex::new(vec![])),
|
||||
stored: Arc::new(Mutex::new(vec![])),
|
||||
};
|
||||
crate::init_stderr_logging();
|
||||
let handler2 = handler.clone();
|
||||
let _smtp_handle = thread::spawn(move || {
|
||||
let address = handler2.address.clone();
|
||||
let ssl = handler2.ssl.clone();
|
||||
|
||||
let mut server = Server::new(handler2.clone());
|
||||
let sockaddr = address.as_ref().to_socket_addrs().unwrap().next().unwrap();
|
||||
let ip = sockaddr.ip();
|
||||
let port = sockaddr.port();
|
||||
let addr = std::net::SocketAddr::new(ip, port);
|
||||
eprintln!("Running smtp server at {}", addr);
|
||||
server
|
||||
.with_name("example.com")
|
||||
.with_ssl((&ssl).into())
|
||||
.unwrap()
|
||||
.with_addr(addr)
|
||||
.unwrap();
|
||||
server.serve().expect("Could not run server");
|
||||
});
|
||||
handler
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirror struct for [`mailin_embedded::SslConfig`] because it does not
|
||||
/// implement Debug or Clone.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SslConfig {
|
||||
/// Do not support STARTTLS
|
||||
None,
|
||||
/// Use a self-signed certificate for STARTTLS
|
||||
SelfSigned {
|
||||
/// Certificate path
|
||||
cert_path: String,
|
||||
/// Path to key file
|
||||
key_path: String,
|
||||
},
|
||||
/// Use a certificate from an authority
|
||||
Trusted {
|
||||
/// Certificate path
|
||||
cert_path: String,
|
||||
/// Key file path
|
||||
key_path: String,
|
||||
/// Path to CA bundle
|
||||
chain_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<&SslConfig> for mailin_embedded::SslConfig {
|
||||
fn from(val: &SslConfig) -> Self {
|
||||
match val {
|
||||
SslConfig::None => Self::None,
|
||||
SslConfig::SelfSigned {
|
||||
ref cert_path,
|
||||
ref key_path,
|
||||
} => Self::SelfSigned {
|
||||
cert_path: cert_path.to_string(),
|
||||
key_path: key_path.to_string(),
|
||||
},
|
||||
SslConfig::Trusted {
|
||||
ref cert_path,
|
||||
ref key_path,
|
||||
ref chain_path,
|
||||
} => Self::Trusted {
|
||||
cert_path: cert_path.to_string(),
|
||||
key_path: key_path.to_string(),
|
||||
chain_path: chain_path.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue