From ae0d5969ad9541c6051923599c886ce0131e59da Mon Sep 17 00:00:00 2001 From: boxbeam Date: Fri, 16 Feb 2024 16:16:34 -0500 Subject: [PATCH] refactor(webserver): Refactor test SMTP server utils (#1471) * refactor(webserver): Refactor test SMTP server utils * Apply suggested changes * Move module to testutils * Rename method * Separate start and create_test_email_service * Fix test --- ee/tabby-webserver/src/schema/auth.rs | 3 +- ee/tabby-webserver/src/service/auth.rs | 42 ++++++++----- ee/tabby-webserver/src/service/email/mod.rs | 34 ++++------- .../src/service/email/test_utils.rs | 42 ------------- .../src/service/email/testutils.rs | 61 +++++++++++++++++++ 5 files changed, 101 insertions(+), 81 deletions(-) delete mode 100644 ee/tabby-webserver/src/service/email/test_utils.rs create mode 100644 ee/tabby-webserver/src/service/email/testutils.rs diff --git a/ee/tabby-webserver/src/schema/auth.rs b/ee/tabby-webserver/src/schema/auth.rs index 725acbe6432d..10aa58ec53c8 100644 --- a/ee/tabby-webserver/src/schema/auth.rs +++ b/ee/tabby-webserver/src/schema/auth.rs @@ -12,6 +12,7 @@ use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use tabby_common::terminal::{HeaderFormat, InfoMessage}; use thiserror::Error; +use tokio::task::JoinHandle; use tracing::{error, warn}; use uuid::Uuid; use validator::{Validate, ValidationErrors}; @@ -447,7 +448,7 @@ pub trait AuthenticationService: Send + Sync { async fn reset_user_auth_token(&self, email: &str) -> Result<()>; async fn password_reset(&self, code: &str, password: &str) -> Result<(), PasswordResetError>; - async fn request_password_reset_email(&self, email: String) -> Result<()>; + async fn request_password_reset_email(&self, email: String) -> Result>>; async fn list_users( &self, diff --git a/ee/tabby-webserver/src/service/auth.rs b/ee/tabby-webserver/src/service/auth.rs index 8a212f5574f6..41b92c46523f 100644 --- a/ee/tabby-webserver/src/service/auth.rs +++ b/ee/tabby-webserver/src/service/auth.rs @@ -10,6 +10,7 @@ use async_trait::async_trait; use chrono::{Duration, Utc}; use juniper::ID; use tabby_db::{DbConn, InvitationDAO}; +use tokio::task::JoinHandle; use tracing::warn; use validator::{Validate, ValidationError}; @@ -219,11 +220,11 @@ impl AuthenticationService for AuthenticationServiceImpl { Ok(resp) } - async fn request_password_reset_email(&self, email: String) -> Result<()> { + async fn request_password_reset_email(&self, email: String) -> Result>> { let user = self.get_user_by_email(&email).await.ok(); let Some(user @ User { active: true, .. }) = user else { - return Ok(()); + return Ok(None); }; let id = user.id.as_rowid()?; @@ -236,10 +237,11 @@ impl AuthenticationService for AuthenticationServiceImpl { } } let code = self.db.create_password_reset(id as i64).await?; - self.mail + let handle = self + .mail .send_password_reset_email(user.email, code.clone()) .await?; - Ok(()) + Ok(Some(handle)) } async fn password_reset(&self, code: &str, password: &str) -> Result<(), PasswordResetError> { @@ -594,15 +596,23 @@ mod tests { } } + async fn test_authentication_service_with_mail() -> (AuthenticationServiceImpl, TestEmailServer) + { + let db = DbConn::new_in_memory().await.unwrap(); + let smtp = TestEmailServer::start().await; + let service = AuthenticationServiceImpl { + db: db.clone(), + mail: Arc::new(smtp.create_test_email_service(db).await), + }; + (service, smtp) + } + use assert_matches::assert_matches; use juniper_axum::relay::{self, Connection}; use serial_test::serial; use super::*; - use crate::service::email::{ - new_email_service, - test_utils::{default_email_settings, start_smtp_server}, - }; + use crate::service::email::{new_email_service, testutils::TestEmailServer}; #[test] fn test_password_hash() { @@ -886,13 +896,7 @@ mod tests { #[tokio::test] #[serial] async fn test_password_reset() { - let service = test_authentication_service().await; - service - .mail - .update_email_setting(default_email_settings()) - .await - .unwrap(); - let _smtp = start_smtp_server().await; + let (service, smtp) = test_authentication_service_with_mail().await; // Test first reset, ensure wrong code fails service @@ -902,10 +906,16 @@ mod tests { .unwrap(); let user = service.get_user_by_email("user@example.com").await.unwrap(); - service + let handle = service .request_password_reset_email("user@example.com".into()) .await .unwrap(); + handle.unwrap().await.unwrap(); + assert!(smtp.list_mail().await[0] + .subject + .to_lowercase() + .contains("password")); + let reset = service .db .get_password_reset_by_user_id(user.id.as_rowid().unwrap() as i64) diff --git a/ee/tabby-webserver/src/service/email/mod.rs b/ee/tabby-webserver/src/service/email/mod.rs index 06095eaed790..4cea9d84c69c 100644 --- a/ee/tabby-webserver/src/service/email/mod.rs +++ b/ee/tabby-webserver/src/service/email/mod.rs @@ -16,7 +16,7 @@ use tokio::{sync::RwLock, task::JoinHandle}; use tracing::warn; mod templates; #[cfg(test)] -pub mod test_utils; +pub mod testutils; use crate::schema::{ email::{ @@ -258,11 +258,9 @@ fn to_address(email: String) -> Result
{ #[cfg(test)] mod tests { - use serde::Deserialize; use serial_test::serial; - use super::*; - use crate::service::email::test_utils::setup_test_email_service; + use super::{testutils::TestEmailServer, *}; #[tokio::test] async fn test_update_email_with_service() { @@ -285,27 +283,16 @@ mod tests { service.delete_email_setting().await.unwrap(); } - #[derive(Deserialize, Debug)] - struct Mail { - sender: String, - subject: String, - } - - async fn read_mails() -> Vec { - let mails = reqwest::get("http://localhost:1080/api/messages") - .await - .unwrap(); - - mails.json().await.unwrap() - } - /* * Requires https://github.com/mailtutan/mailtutan */ #[tokio::test] #[serial] async fn test_send_email() { - let (service, _child) = setup_test_email_service().await; + let mail_server = TestEmailServer::start().await; + let service = mail_server + .create_test_email_service(DbConn::new_in_memory().await.unwrap()) + .await; let handle = service .send_invitation_email("user@localhost".into(), "12345".into()) @@ -321,7 +308,7 @@ mod tests { handle.await.unwrap(); - let mails = read_mails().await; + let mails = mail_server.list_mail().await; let default_from = service .read_email_setting() .await @@ -334,7 +321,10 @@ mod tests { #[tokio::test] #[serial] async fn test_send_test_email() { - let (service, _child) = setup_test_email_service().await; + let mail_server = TestEmailServer::start().await; + let service = mail_server + .create_test_email_service(DbConn::new_in_memory().await.unwrap()) + .await; let handle = service .send_test_email("user@localhost".into()) @@ -343,7 +333,7 @@ mod tests { handle.await.unwrap(); - let mails = read_mails().await; + let mails = mail_server.list_mail().await; assert_eq!(mails[0].subject, templates::test().subject); } } diff --git a/ee/tabby-webserver/src/service/email/test_utils.rs b/ee/tabby-webserver/src/service/email/test_utils.rs deleted file mode 100644 index 25f1a02e9085..000000000000 --- a/ee/tabby-webserver/src/service/email/test_utils.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::time::Duration; - -use tabby_db::DbConn; -use tokio::process::{Child, Command}; - -use super::new_email_service; -use crate::schema::email::{AuthMethod, EmailService, EmailSettingInput, Encryption}; - -pub fn default_email_settings() -> EmailSettingInput { - EmailSettingInput { - smtp_username: "tabby".into(), - smtp_server: "127.0.0.1".into(), - smtp_port: 1025, - from_address: "tabby@localhost".into(), - encryption: Encryption::None, - auth_method: AuthMethod::None, - smtp_password: Some("fake".into()), - } -} - -pub async fn start_smtp_server() -> Child { - let mut cmd = Command::new("mailtutan"); - cmd.kill_on_drop(true); - - let child = cmd - .spawn() - .expect("You need to run `cargo install mailtutan` before running this test"); - tokio::time::sleep(Duration::from_secs(1)).await; - child -} - -pub async fn setup_test_email_service() -> (impl EmailService, Child) { - let child = start_smtp_server().await; - - let db = DbConn::new_in_memory().await.unwrap(); - let service = new_email_service(db).await.unwrap(); - service - .update_email_setting(default_email_settings()) - .await - .unwrap(); - (service, child) -} diff --git a/ee/tabby-webserver/src/service/email/testutils.rs b/ee/tabby-webserver/src/service/email/testutils.rs new file mode 100644 index 000000000000..8acf1c9acba4 --- /dev/null +++ b/ee/tabby-webserver/src/service/email/testutils.rs @@ -0,0 +1,61 @@ +use std::time::Duration; + +use serde::Deserialize; +use tabby_db::DbConn; +use tokio::process::{Child, Command}; + +use super::new_email_service; +use crate::schema::email::{AuthMethod, EmailService, EmailSettingInput, Encryption}; + +#[derive(Deserialize, Debug)] +pub struct Mail { + pub sender: String, + pub subject: String, +} + +pub struct TestEmailServer { + #[allow(unused)] + child: Child, +} + +impl TestEmailServer { + pub async fn list_mail(&self) -> Vec { + let mails = reqwest::get("http://localhost:1080/api/messages") + .await + .unwrap(); + + mails.json().await.unwrap() + } + + pub async fn create_test_email_service(&self, db_conn: DbConn) -> impl EmailService { + let service = new_email_service(db_conn).await.unwrap(); + service + .update_email_setting(default_email_settings()) + .await + .unwrap(); + service + } + + pub async fn start() -> TestEmailServer { + let mut cmd = Command::new("mailtutan"); + cmd.kill_on_drop(true); + + let child = cmd + .spawn() + .expect("You need to run `cargo install mailtutan` before running this test"); + tokio::time::sleep(Duration::from_secs(1)).await; + TestEmailServer { child } + } +} + +fn default_email_settings() -> EmailSettingInput { + EmailSettingInput { + smtp_username: "tabby".into(), + smtp_server: "127.0.0.1".into(), + smtp_port: 1025, + from_address: "tabby@localhost".into(), + encryption: Encryption::None, + auth_method: AuthMethod::None, + smtp_password: Some("fake".into()), + } +}