Skip to content

Commit

Permalink
tests: [#111] E2E test for user routes
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Apr 25, 2023
1 parent f257692 commit 2b58923
Show file tree
Hide file tree
Showing 18 changed files with 482 additions and 48 deletions.
10 changes: 10 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ services:
- ~/.cargo:/home/appuser/.cargo
depends_on:
- tracker
- mailcatcher
- mysql

tracker:
image: torrust/tracker:develop
Expand Down Expand Up @@ -62,6 +64,14 @@ services:
depends_on:
- mysql

mailcatcher:
image: dockage/mailcatcher:0.8.2
networks:
- server_side
ports:
- 1080:1080
- 1025:1025

mysql:
image: mysql:8.0
command: '--default-authentication-plugin=mysql_native_password'
Expand Down
6 changes: 3 additions & 3 deletions config-idx-back.toml.local
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ max_password_length = 64
secret_key = "MaxVerstappenWC2021"

[database]
connect_url = "sqlite://storage/database/data.db?mode=rwc" # SQLite
connect_url = "sqlite://storage/database/torrust_index_backend_e2e_testing.db?mode=rwc" # SQLite
#connect_url = "mysql://root:root_secret_password@mysql:3306/torrust_index_backend" # MySQL
torrent_info_update_interval = 3600

Expand All @@ -28,5 +28,5 @@ from = "example@email.com"
reply_to = "noreply@email.com"
username = ""
password = ""
server = ""
port = 25
server = "mailcatcher"
port = 1025
2 changes: 1 addition & 1 deletion config-tracker.toml.local
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
log_level = "info"
mode = "public"
db_driver = "Sqlite3"
db_path = "./storage/database/tracker.db"
db_path = "./storage/database/torrust_tracker_e2e_testing.db"
announce_interval = 120
min_announce_interval = 120
max_peer_timeout = 900
Expand Down
1 change: 1 addition & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Leechers
LEECHERS
lettre
luckythelab
mailcatcher
nanos
NCCA
nilm
Expand Down
22 changes: 15 additions & 7 deletions src/mailer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,21 @@ impl MailerService {
async fn get_mailer(cfg: &Configuration) -> Mailer {
let settings = cfg.settings.read().await;

let creds = Credentials::new(settings.mail.username.to_owned(), settings.mail.password.to_owned());

AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
.port(settings.mail.port)
.credentials(creds)
.authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain])
.build()
if !settings.mail.username.is_empty() && !settings.mail.password.is_empty() {
// SMTP authentication
let creds = Credentials::new(settings.mail.username.clone(), settings.mail.password.clone());

AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
.port(settings.mail.port)
.credentials(creds)
.authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain])
.build()
} else {
// SMTP without authentication
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
.port(settings.mail.port)
.build()
}
}

pub async fn send_verification_mail(
Expand Down
8 changes: 8 additions & 0 deletions src/routes/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder};
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use log::{debug, info};
use pbkdf2::Pbkdf2;
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
Expand All @@ -20,6 +21,7 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) {
web::scope("/user")
.service(web::resource("/register").route(web::post().to(register)))
.service(web::resource("/login").route(web::post().to(login)))
// code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user.
.service(web::resource("/ban/{user}").route(web::delete().to(ban_user)))
.service(web::resource("/token/verify").route(web::post().to(verify_token)))
.service(web::resource("/token/renew").route(web::post().to(renew_token)))
Expand Down Expand Up @@ -47,6 +49,8 @@ pub struct Token {
}

pub async fn register(req: HttpRequest, mut payload: web::Json<Register>, app_data: WebAppData) -> ServiceResult<impl Responder> {
info!("registering user: {}", payload.username);

let settings = app_data.cfg.settings.read().await;

match settings.auth.email_on_signup {
Expand Down Expand Up @@ -253,6 +257,8 @@ pub async fn verify_email(req: HttpRequest, app_data: WebAppData) -> String {

// TODO: add reason and date_expiry parameters to request
pub async fn ban_user(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
debug!("banning user");

let user = app_data.auth.get_user_compact_from_request(&req).await?;

// check if user is administrator
Expand All @@ -262,6 +268,8 @@ pub async fn ban_user(req: HttpRequest, app_data: WebAppData) -> ServiceResult<i

let to_be_banned_username = req.match_info().get("user").unwrap();

debug!("user to be banned: {}", to_be_banned_username);

let user_profile = app_data
.database
.get_user_profile_from_username(to_be_banned_username)
Expand Down
12 changes: 9 additions & 3 deletions tests/e2e/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,23 @@ pub fn assert_response_title(response: &Response, title: &str) {

pub fn assert_text_ok(response: &Response) {
assert_eq!(response.status, 200);
assert_eq!(response.content_type, "text/html; charset=utf-8");
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "text/html; charset=utf-8");
}
}

pub fn _assert_text_bad_request(response: &Response) {
assert_eq!(response.status, 400);
assert_eq!(response.content_type, "text/plain; charset=utf-8");
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "text/plain; charset=utf-8");
}
}

// JSON responses

pub fn assert_json_ok(response: &Response) {
assert_eq!(response.status, 200);
assert_eq!(response.content_type, "application/json");
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "application/json");
}
}
99 changes: 77 additions & 22 deletions tests/e2e/client.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,112 @@
use reqwest::Response as ReqwestResponse;
use serde::Serialize;

use super::contexts::user::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username};
use crate::e2e::connection_info::ConnectionInfo;
use crate::e2e::http::{Query, ReqwestQuery};
use crate::e2e::response::Response;

/// API Client
pub struct Client {
connection_info: ConnectionInfo,
base_path: String,
http_client: Http,
}

impl Client {
pub fn new(connection_info: ConnectionInfo) -> Self {
Self {
connection_info,
base_path: "/".to_string(),
http_client: Http::new(connection_info),
}
}

pub async fn root(&self) -> Response {
self.get("", Query::empty()).await
}
// Context: about

pub async fn about(&self) -> Response {
self.get("about", Query::empty()).await
self.http_client.get("about", Query::empty()).await
}

pub async fn license(&self) -> Response {
self.get("about/license", Query::empty()).await
self.http_client.get("about/license", Query::empty()).await
}

pub async fn get(&self, path: &str, params: Query) -> Response {
self.get_request_with_query(path, params).await
// Context: category

pub async fn get_categories(&self) -> Response {
self.http_client.get("category", Query::empty()).await
}

/*
pub async fn post(&self, path: &str) -> Response {
let response = reqwest::Client::new().post(self.base_url(path).clone()).send().await.unwrap();
Response::from(response).await
// Context: root

pub async fn root(&self) -> Response {
self.http_client.get("", Query::empty()).await
}

async fn delete(&self, path: &str) -> Response {
reqwest::Client::new()
.delete(self.base_url(path).clone())
// Context: user

pub async fn register_user(&self, registration_form: RegistrationForm) -> Response {
self.http_client.post("user/register", &registration_form).await
}

pub async fn login_user(&self, registration_form: LoginForm) -> Response {
self.http_client.post("user/login", &registration_form).await
}

pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> Response {
self.http_client.post("user/token/verify", &token_verification_form).await
}

pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> Response {
self.http_client.post("user/token/renew", &token_verification_form).await
}

pub async fn ban_user(&self, username: Username) -> Response {
self.http_client.delete(&format!("user/ban/{}", &username.value)).await
}
}

/// Generic HTTP Client
struct Http {
connection_info: ConnectionInfo,
base_path: String,
}

impl Http {
pub fn new(connection_info: ConnectionInfo) -> Self {
Self {
connection_info,
base_path: "/".to_string(),
}
}

pub async fn get(&self, path: &str, params: Query) -> Response {
self.get_request_with_query(path, params).await
}

pub async fn post<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> Response {
let response = reqwest::Client::new()
.post(self.base_url(path).clone())
.json(&form)
.send()
.await
.unwrap()
.unwrap();
Response::from(response).await
}

pub async fn get_request(&self, path: &str) -> Response {
get(&self.base_url(path), None).await
async fn delete(&self, path: &str) -> Response {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::new()
.delete(self.base_url(path).clone())
.bearer_auth(token)
.send()
.await
.unwrap(),
None => reqwest::Client::new()
.delete(self.base_url(path).clone())
.send()
.await
.unwrap(),
};
Response::from(response).await
}
*/

pub async fn get_request_with_query(&self, path: &str, params: Query) -> Response {
get(&self.base_url(path), Some(params)).await
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e/connection_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@ pub fn anonymous_connection(bind_address: &str) -> ConnectionInfo {
ConnectionInfo::anonymous(bind_address)
}

pub fn authenticated_connection(bind_address: &str, token: &str) -> ConnectionInfo {
ConnectionInfo::new(bind_address, token)
}

#[derive(Clone)]
pub struct ConnectionInfo {
pub bind_address: String,
pub token: Option<String>,
}

impl ConnectionInfo {
pub fn new(bind_address: &str, token: &str) -> Self {
Self {
bind_address: bind_address.to_string(),
token: Some(token.to_string()),
}
}

pub fn anonymous(bind_address: &str) -> Self {
Self {
bind_address: bind_address.to_string(),
token: None,
}
}
}
2 changes: 1 addition & 1 deletion tests/e2e/routes/about.rs → tests/e2e/contexts/about.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::e2e::asserts::{assert_response_title, assert_text_ok};
use crate::e2e::env::TestEnv;
use crate::e2e::environment::TestEnv;

#[tokio::test]
#[cfg_attr(not(feature = "e2e-tests"), ignore)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::e2e::asserts::assert_json_ok;
use crate::e2e::env::TestEnv;
use crate::e2e::http::Query;
use crate::e2e::environment::TestEnv;

#[tokio::test]
#[cfg_attr(not(feature = "e2e-tests"), ignore)]
async fn it_should_return_an_empty_category_list_when_there_are_no_categories() {
let client = TestEnv::default().unauthenticated_client();

let response = client.get("category", Query::empty()).await;
let response = client.get_categories().await;

assert_json_ok(&response);
}
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/routes/mod.rs → tests/e2e/contexts/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod about;
pub mod category;
pub mod root;
pub mod user;
2 changes: 1 addition & 1 deletion tests/e2e/routes/root.rs → tests/e2e/contexts/root.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::e2e::asserts::{assert_response_title, assert_text_ok};
use crate::e2e::env::TestEnv;
use crate::e2e::environment::TestEnv;

#[tokio::test]
#[cfg_attr(not(feature = "e2e-tests"), ignore)]
Expand Down
Loading

0 comments on commit 2b58923

Please sign in to comment.