mirror of
https://gitlab.com/hagrid-keyserver/hagrid.git
synced 2023-02-13 20:55:02 -05:00
mail: add tests
This commit is contained in:
parent
09d71a2a4f
commit
d3dd37666d
4 changed files with 202 additions and 42 deletions
199
src/mail.rs
199
src/mail.rs
|
@ -1,4 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{PathBuf, Path};
|
||||
|
||||
use failure;
|
||||
use handlebars::Handlebars;
|
||||
|
@ -63,20 +63,20 @@ enum Transport {
|
|||
|
||||
impl Service {
|
||||
/// Sends mail via sendmail.
|
||||
pub fn sendmail(from: String, base_uri: String, template_dir: PathBuf) -> Result<Self> {
|
||||
pub fn sendmail(from: &str, base_uri: &str, template_dir: &Path) -> Result<Self> {
|
||||
Self::new(from, base_uri, template_dir, Transport::Sendmail)
|
||||
}
|
||||
|
||||
/// Sends mail by storing it in the given directory.
|
||||
pub fn filemail(from: String, base_uri: String, template_dir: PathBuf, path: PathBuf) -> Result<Self> {
|
||||
Self::new(from, base_uri, template_dir, Transport::Filemail(path))
|
||||
pub fn filemail(from: &str, base_uri: &str, template_dir: &Path, path: &Path) -> Result<Self> {
|
||||
Self::new(from, base_uri, template_dir, Transport::Filemail(path.to_owned()))
|
||||
}
|
||||
|
||||
fn new(from: String, base_uri: String, template_dir: PathBuf, transport: Transport)
|
||||
fn new(from: &str, base_uri: &str, template_dir: &Path, transport: Transport)
|
||||
-> Result<Self> {
|
||||
let templates = template_helpers::load_handlebars(template_dir)?;
|
||||
let domain =
|
||||
url::Url::parse(&base_uri)
|
||||
url::Url::parse(base_uri)
|
||||
?.host_str().ok_or_else(|| failure::err_msg("No host in base-URI"))
|
||||
?.to_string();
|
||||
Ok(Self { from: from.into(), domain, templates, transport })
|
||||
|
@ -248,3 +248,190 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// for some reason, this is no longer public in lettre itself
|
||||
// FIXME replace with builtin struct on lettre update
|
||||
// see https://github.com/lettre/lettre/blob/master/lettre/src/file/mod.rs#L41
|
||||
#[cfg(test)]
|
||||
#[derive(Deserialize)]
|
||||
struct SerializableEmail {
|
||||
#[serde(alias = "envelope")]
|
||||
_envelope: lettre::Envelope,
|
||||
#[serde(alias = "message_id")]
|
||||
_message_id: String,
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Returns and removes the first mail it finds from the given
|
||||
/// directory.
|
||||
#[cfg(test)]
|
||||
pub fn pop_mail(dir: &Path) -> Result<Option<String>> {
|
||||
use std::fs;
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
if entry.file_type()?.is_file() {
|
||||
let fh = fs::File::open(entry.path())?;
|
||||
fs::remove_file(entry.path())?;
|
||||
let mail: SerializableEmail = ::serde_json::from_reader(fh)?;
|
||||
let body = String::from_utf8_lossy(&mail.message).to_string();
|
||||
return Ok(Some(body));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use gettext_macros::{include_i18n};
|
||||
use std::str::FromStr;
|
||||
|
||||
const BASEDIR: &str = "http://localhost/";
|
||||
const FROM: &str = "test@localhost";
|
||||
const TO: &str = "recipient@example.org";
|
||||
|
||||
fn configure_i18n(lang: &'static str) -> I18n {
|
||||
let langs = include_i18n!();
|
||||
let catalog = langs.clone().into_iter().find(|(l, _)| *l == lang).unwrap().1;
|
||||
rocket_i18n::I18n { catalog, lang }
|
||||
}
|
||||
|
||||
fn configure_mail() -> (Service, TempDir) {
|
||||
let template_dir: PathBuf = ::std::env::current_dir().unwrap().join("dist/email-templates").to_str().unwrap().into();
|
||||
let tempdir = tempdir().unwrap();
|
||||
let service = Service::filemail(FROM, BASEDIR, &template_dir, tempdir.path()).unwrap();
|
||||
(service, tempdir)
|
||||
}
|
||||
|
||||
fn assert_header(headers: &[(&str, &str)], name: &str, pred: impl Fn(&str) -> bool) {
|
||||
if let Some((_, v)) = headers.iter().find(|(h, _)| *h == name) {
|
||||
assert!(pred(v));
|
||||
} else {
|
||||
panic!(format!("Missing header: {}", name));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_headers(mail_content: &str) {
|
||||
// this naively assumes that all lines colons are headers, and that all headers fit in
|
||||
// a single line. that's not accurate, but ok for our testing.
|
||||
let headers: Vec<_> = mail_content
|
||||
.lines()
|
||||
.filter(|line| line.contains(": "))
|
||||
.map(|line| {
|
||||
let mut it = line.splitn(2, ": ");
|
||||
let h = it.next().unwrap();
|
||||
let v = it.next().unwrap();
|
||||
(h, v)
|
||||
})
|
||||
.collect();
|
||||
assert!(headers.contains(&("Content-Transfer-Encoding", "8bit")));
|
||||
assert!(headers.contains(&("Content-Type", "text/plain; charset=utf-8")));
|
||||
assert!(headers.contains(&("Content-Type", "text/html; charset=utf-8")));
|
||||
assert!(headers.contains(&("From", "<test@localhost>")));
|
||||
assert!(headers.contains(&("To", "<recipient@example.org>")));
|
||||
assert_header(&headers, "Content-Type", |v| v.starts_with("multipart/alternative"));
|
||||
assert_header(&headers, "Date", |v| v.contains("+0000"));
|
||||
assert_header(&headers, "Message-ID", |v| v.contains("@localhost>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_mail_empty() {
|
||||
let (_mail, tempdir) = configure_mail();
|
||||
assert!(pop_mail(tempdir.path()).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_verification_mail_en() {
|
||||
let (mail, tempdir) = configure_mail();
|
||||
let i18n = configure_i18n("en");
|
||||
let recipient = Email::from_str(TO).unwrap();
|
||||
|
||||
mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
|
||||
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
|
||||
|
||||
check_headers(&mail_content);
|
||||
assert!(mail_content.contains("lang=\"en\""));
|
||||
assert!(mail_content.contains("Hi,"));
|
||||
assert!(mail_content.contains("fingerprintoo"));
|
||||
assert!(mail_content.contains("test/verify/token"));
|
||||
assert!(mail_content.contains("test/about"));
|
||||
assert!(mail_content.contains("To let others find this key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_verification_mail_ja() {
|
||||
let (mail, tempdir) = configure_mail();
|
||||
let i18n = configure_i18n("ja");
|
||||
let recipient = Email::from_str(TO).unwrap();
|
||||
|
||||
mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
|
||||
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
|
||||
|
||||
check_headers(&mail_content);
|
||||
assert!(mail_content.contains("lang=\"ja\""));
|
||||
assert!(mail_content.contains("どうも、"));
|
||||
assert!(mail_content.contains("fingerprintoo"));
|
||||
assert!(mail_content.contains("test/verify/token"));
|
||||
assert!(mail_content.contains("test/about"));
|
||||
assert!(mail_content.contains("あなたのメールアド"));
|
||||
assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E3=81=82=E3=81=AA=E3=81=9F=E3=81=AE?="));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_manage_mail_en() {
|
||||
let (mail, tempdir) = configure_mail();
|
||||
let i18n = configure_i18n("en");
|
||||
let recipient = Email::from_str(TO).unwrap();
|
||||
|
||||
mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
|
||||
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
|
||||
|
||||
check_headers(&mail_content);
|
||||
assert!(mail_content.contains("lang=\"en\""));
|
||||
assert!(mail_content.contains("Hi,"));
|
||||
assert!(mail_content.contains("fingerprintoo"));
|
||||
assert!(mail_content.contains("testtoken"));
|
||||
assert!(mail_content.contains("test/about"));
|
||||
assert!(mail_content.contains("manage and delete"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_manage_mail_ja() {
|
||||
let (mail, tempdir) = configure_mail();
|
||||
let i18n = configure_i18n("ja");
|
||||
let recipient = Email::from_str(TO).unwrap();
|
||||
|
||||
mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
|
||||
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
|
||||
|
||||
check_headers(&mail_content);
|
||||
print!("{}", mail_content);
|
||||
assert!(mail_content.contains("lang=\"ja\""));
|
||||
assert!(mail_content.contains("どうも、"));
|
||||
assert!(mail_content.contains("fingerprintoo"));
|
||||
assert!(mail_content.contains("testtoken"));
|
||||
assert!(mail_content.contains("test/about"));
|
||||
assert!(mail_content.contains("この鍵の掲示されたア"));
|
||||
assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E9=8D=B5=E3=82=92=E7=AE=A1=E7=90=86?="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_welcome_mail() {
|
||||
let (mail, tempdir) = configure_mail();
|
||||
let recipient = Email::from_str(TO).unwrap();
|
||||
|
||||
mail.send_welcome("test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
|
||||
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
|
||||
|
||||
check_headers(&mail_content);
|
||||
assert!(mail_content.contains("lang=\"en\""));
|
||||
assert!(mail_content.contains("Hi,"));
|
||||
assert!(mail_content.contains("fingerprintoo"));
|
||||
assert!(mail_content.contains("test/upload/token"));
|
||||
assert!(mail_content.contains("test/about"));
|
||||
assert!(mail_content.contains("first time"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> R
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn load_handlebars(template_dir: PathBuf) -> Result<Handlebars> {
|
||||
pub fn load_handlebars(template_dir: &Path) -> Result<Handlebars> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
let i18ns = include_i18n!();
|
||||
|
@ -60,7 +60,7 @@ pub fn load_handlebars(template_dir: PathBuf) -> Result<Handlebars> {
|
|||
let glob_path = glob_path.to_str().expect("valid glob path string");
|
||||
|
||||
for path in glob::glob(glob_path).unwrap().flatten() {
|
||||
let template_name = remove_extension(path.strip_prefix(&template_dir)?);
|
||||
let template_name = remove_extension(path.strip_prefix(template_dir)?);
|
||||
handlebars.register_template_file(&template_name.to_string_lossy(), &path)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -282,6 +282,7 @@ mod tests {
|
|||
use sequoia_openpgp::serialize::Serialize;
|
||||
|
||||
use crate::web::tests::*;
|
||||
use crate::mail::pop_mail;
|
||||
|
||||
#[test]
|
||||
fn hkp() {
|
||||
|
|
|
@ -519,16 +519,16 @@ fn configure_mail_service(config: &Config) -> Result<mail::Service> {
|
|||
// Mail service
|
||||
let email_template_dir: PathBuf = config.get_str("email_template_dir")?.into();
|
||||
|
||||
let base_uri = config.get_str("base-URI")?.to_string();
|
||||
let from = config.get_str("from")?.to_string();
|
||||
let base_uri = config.get_str("base-URI")?;
|
||||
let from = config.get_str("from")?;
|
||||
|
||||
let filemail_into = config.get_str("filemail_into")
|
||||
.ok().map(|p| PathBuf::from(p));
|
||||
|
||||
if let Some(path) = filemail_into {
|
||||
mail::Service::filemail(from, base_uri, email_template_dir, path)
|
||||
mail::Service::filemail(from, base_uri, &email_template_dir, &path)
|
||||
} else {
|
||||
mail::Service::sendmail(from, base_uri, email_template_dir)
|
||||
mail::Service::sendmail(from, base_uri, &email_template_dir)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -561,7 +561,6 @@ pub mod tests {
|
|||
use rocket::local::{Client, LocalResponse};
|
||||
use rocket::http::Status;
|
||||
use rocket::http::ContentType;
|
||||
use lettre::Envelope;
|
||||
|
||||
use sequoia_openpgp::Cert;
|
||||
use sequoia_openpgp::cert::CertBuilder;
|
||||
|
@ -570,21 +569,11 @@ pub mod tests {
|
|||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use mail::pop_mail;
|
||||
|
||||
use crate::database::*;
|
||||
use super::*;
|
||||
|
||||
// for some reason, this is no longer public in lettre itself
|
||||
// FIXME replace with builtin struct on lettre update
|
||||
// see https://github.com/lettre/lettre/blob/master/lettre/src/file/mod.rs#L41
|
||||
#[derive(Deserialize)]
|
||||
struct SerializableEmail {
|
||||
#[serde(alias = "envelope")]
|
||||
_envelope: Envelope,
|
||||
#[serde(alias = "message_id")]
|
||||
_message_id: String,
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Fake base URI to use in tests.
|
||||
const BASE_URI: &'static str = "http://local.connection";
|
||||
const BASE_URI_ONION: &'static str = "http://local.connection.onion";
|
||||
|
@ -647,7 +636,6 @@ pub mod tests {
|
|||
Ok((tmpdir, Client::new(rocket)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn assert_consistency(rocket: &rocket::Rocket) {
|
||||
let db = rocket.state::<KeyDatabase>().unwrap();
|
||||
db.check_consistency().unwrap();
|
||||
|
@ -1241,22 +1229,6 @@ pub mod tests {
|
|||
String::from_utf8_lossy(capture_content).to_string()
|
||||
}
|
||||
|
||||
/// Returns and removes the first mail it finds from the given
|
||||
/// directory.
|
||||
pub fn pop_mail(dir: &Path) -> Result<Option<String>> {
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
if entry.file_type()?.is_file() {
|
||||
let fh = fs::File::open(entry.path())?;
|
||||
fs::remove_file(entry.path())?;
|
||||
let mail: SerializableEmail = ::serde_json::from_reader(fh)?;
|
||||
let body = String::from_utf8_lossy(&mail.message).to_string();
|
||||
return Ok(Some(body));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn vks_publish_submit_multiple<'a>(client: &'a Client, data: &[u8]) {
|
||||
let mut response = vks_publish_submit_response(client, data);
|
||||
let response_body = response.body_string().unwrap();
|
||||
|
|
Loading…
Reference in a new issue