Skip to content

Commit

Permalink
refactor(webserver): Refactor test SMTP server utils (#1471)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
boxbeam authored Feb 16, 2024
1 parent 35fccce commit ae0d596
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 81 deletions.
3 changes: 2 additions & 1 deletion ee/tabby-webserver/src/schema/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Option<JoinHandle<()>>>;

async fn list_users(
&self,
Expand Down
42 changes: 26 additions & 16 deletions ee/tabby-webserver/src/service/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<Option<JoinHandle<()>>> {
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()?;
Expand All @@ -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> {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -902,10 +906,16 @@ mod tests {
.unwrap();
let user = service.get_user_by_email("[email protected]").await.unwrap();

service
let handle = service
.request_password_reset_email("[email protected]".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)
Expand Down
34 changes: 12 additions & 22 deletions ee/tabby-webserver/src/service/email/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -258,11 +258,9 @@ fn to_address(email: String) -> Result<Address> {

#[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() {
Expand All @@ -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<Mail> {
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())
Expand All @@ -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
Expand All @@ -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())
Expand All @@ -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);
}
}
42 changes: 0 additions & 42 deletions ee/tabby-webserver/src/service/email/test_utils.rs

This file was deleted.

61 changes: 61 additions & 0 deletions ee/tabby-webserver/src/service/email/testutils.rs
Original file line number Diff line number Diff line change
@@ -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<Mail> {
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()),
}
}

0 comments on commit ae0d596

Please sign in to comment.