Skip to content

Commit 2b58923

Browse files
committed
tests: [#111] E2E test for user routes
1 parent f257692 commit 2b58923

File tree

18 files changed

+482
-48
lines changed

18 files changed

+482
-48
lines changed

compose.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ services:
3333
- ~/.cargo:/home/appuser/.cargo
3434
depends_on:
3535
- tracker
36+
- mailcatcher
37+
- mysql
3638

3739
tracker:
3840
image: torrust/tracker:develop
@@ -62,6 +64,14 @@ services:
6264
depends_on:
6365
- mysql
6466

67+
mailcatcher:
68+
image: dockage/mailcatcher:0.8.2
69+
networks:
70+
- server_side
71+
ports:
72+
- 1080:1080
73+
- 1025:1025
74+
6575
mysql:
6676
image: mysql:8.0
6777
command: '--default-authentication-plugin=mysql_native_password'

config-idx-back.toml.local

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ max_password_length = 64
1818
secret_key = "MaxVerstappenWC2021"
1919

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

@@ -28,5 +28,5 @@ from = "[email protected]"
2828
reply_to = "[email protected]"
2929
username = ""
3030
password = ""
31-
server = ""
32-
port = 25
31+
server = "mailcatcher"
32+
port = 1025

config-tracker.toml.local

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
log_level = "info"
22
mode = "public"
33
db_driver = "Sqlite3"
4-
db_path = "./storage/database/tracker.db"
4+
db_path = "./storage/database/torrust_tracker_e2e_testing.db"
55
announce_interval = 120
66
min_announce_interval = 120
77
max_peer_timeout = 900

project-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Leechers
2323
LEECHERS
2424
lettre
2525
luckythelab
26+
mailcatcher
2627
nanos
2728
NCCA
2829
nilm

src/mailer.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,21 @@ impl MailerService {
4040
async fn get_mailer(cfg: &Configuration) -> Mailer {
4141
let settings = cfg.settings.read().await;
4242

43-
let creds = Credentials::new(settings.mail.username.to_owned(), settings.mail.password.to_owned());
44-
45-
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
46-
.port(settings.mail.port)
47-
.credentials(creds)
48-
.authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain])
49-
.build()
43+
if !settings.mail.username.is_empty() && !settings.mail.password.is_empty() {
44+
// SMTP authentication
45+
let creds = Credentials::new(settings.mail.username.clone(), settings.mail.password.clone());
46+
47+
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
48+
.port(settings.mail.port)
49+
.credentials(creds)
50+
.authentication(vec![Mechanism::Login, Mechanism::Xoauth2, Mechanism::Plain])
51+
.build()
52+
} else {
53+
// SMTP without authentication
54+
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&settings.mail.server)
55+
.port(settings.mail.port)
56+
.build()
57+
}
5058
}
5159

5260
pub async fn send_verification_mail(

src/routes/user.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder};
22
use argon2::password_hash::SaltString;
33
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
44
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
5+
use log::{debug, info};
56
use pbkdf2::Pbkdf2;
67
use rand_core::OsRng;
78
use serde::{Deserialize, Serialize};
@@ -20,6 +21,7 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) {
2021
web::scope("/user")
2122
.service(web::resource("/register").route(web::post().to(register)))
2223
.service(web::resource("/login").route(web::post().to(login)))
24+
// code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user.
2325
.service(web::resource("/ban/{user}").route(web::delete().to(ban_user)))
2426
.service(web::resource("/token/verify").route(web::post().to(verify_token)))
2527
.service(web::resource("/token/renew").route(web::post().to(renew_token)))
@@ -47,6 +49,8 @@ pub struct Token {
4749
}
4850

4951
pub async fn register(req: HttpRequest, mut payload: web::Json<Register>, app_data: WebAppData) -> ServiceResult<impl Responder> {
52+
info!("registering user: {}", payload.username);
53+
5054
let settings = app_data.cfg.settings.read().await;
5155

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

254258
// TODO: add reason and date_expiry parameters to request
255259
pub async fn ban_user(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
260+
debug!("banning user");
261+
256262
let user = app_data.auth.get_user_compact_from_request(&req).await?;
257263

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

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

271+
debug!("user to be banned: {}", to_be_banned_username);
272+
265273
let user_profile = app_data
266274
.database
267275
.get_user_profile_from_username(to_be_banned_username)

tests/e2e/asserts.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,23 @@ pub fn assert_response_title(response: &Response, title: &str) {
1313

1414
pub fn assert_text_ok(response: &Response) {
1515
assert_eq!(response.status, 200);
16-
assert_eq!(response.content_type, "text/html; charset=utf-8");
16+
if let Some(content_type) = &response.content_type {
17+
assert_eq!(content_type, "text/html; charset=utf-8");
18+
}
1719
}
1820

1921
pub fn _assert_text_bad_request(response: &Response) {
2022
assert_eq!(response.status, 400);
21-
assert_eq!(response.content_type, "text/plain; charset=utf-8");
23+
if let Some(content_type) = &response.content_type {
24+
assert_eq!(content_type, "text/plain; charset=utf-8");
25+
}
2226
}
2327

2428
// JSON responses
2529

2630
pub fn assert_json_ok(response: &Response) {
2731
assert_eq!(response.status, 200);
28-
assert_eq!(response.content_type, "application/json");
32+
if let Some(content_type) = &response.content_type {
33+
assert_eq!(content_type, "application/json");
34+
}
2935
}

tests/e2e/client.rs

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,112 @@
11
use reqwest::Response as ReqwestResponse;
2+
use serde::Serialize;
23

4+
use super::contexts::user::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username};
35
use crate::e2e::connection_info::ConnectionInfo;
46
use crate::e2e::http::{Query, ReqwestQuery};
57
use crate::e2e::response::Response;
68

79
/// API Client
810
pub struct Client {
9-
connection_info: ConnectionInfo,
10-
base_path: String,
11+
http_client: Http,
1112
}
1213

1314
impl Client {
1415
pub fn new(connection_info: ConnectionInfo) -> Self {
1516
Self {
16-
connection_info,
17-
base_path: "/".to_string(),
17+
http_client: Http::new(connection_info),
1818
}
1919
}
2020

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

2523
pub async fn about(&self) -> Response {
26-
self.get("about", Query::empty()).await
24+
self.http_client.get("about", Query::empty()).await
2725
}
2826

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

33-
pub async fn get(&self, path: &str, params: Query) -> Response {
34-
self.get_request_with_query(path, params).await
31+
// Context: category
32+
33+
pub async fn get_categories(&self) -> Response {
34+
self.http_client.get("category", Query::empty()).await
3535
}
3636

37-
/*
38-
pub async fn post(&self, path: &str) -> Response {
39-
let response = reqwest::Client::new().post(self.base_url(path).clone()).send().await.unwrap();
40-
Response::from(response).await
37+
// Context: root
38+
39+
pub async fn root(&self) -> Response {
40+
self.http_client.get("", Query::empty()).await
4141
}
4242

43-
async fn delete(&self, path: &str) -> Response {
44-
reqwest::Client::new()
45-
.delete(self.base_url(path).clone())
43+
// Context: user
44+
45+
pub async fn register_user(&self, registration_form: RegistrationForm) -> Response {
46+
self.http_client.post("user/register", &registration_form).await
47+
}
48+
49+
pub async fn login_user(&self, registration_form: LoginForm) -> Response {
50+
self.http_client.post("user/login", &registration_form).await
51+
}
52+
53+
pub async fn verify_token(&self, token_verification_form: TokenVerificationForm) -> Response {
54+
self.http_client.post("user/token/verify", &token_verification_form).await
55+
}
56+
57+
pub async fn renew_token(&self, token_verification_form: TokenRenewalForm) -> Response {
58+
self.http_client.post("user/token/renew", &token_verification_form).await
59+
}
60+
61+
pub async fn ban_user(&self, username: Username) -> Response {
62+
self.http_client.delete(&format!("user/ban/{}", &username.value)).await
63+
}
64+
}
65+
66+
/// Generic HTTP Client
67+
struct Http {
68+
connection_info: ConnectionInfo,
69+
base_path: String,
70+
}
71+
72+
impl Http {
73+
pub fn new(connection_info: ConnectionInfo) -> Self {
74+
Self {
75+
connection_info,
76+
base_path: "/".to_string(),
77+
}
78+
}
79+
80+
pub async fn get(&self, path: &str, params: Query) -> Response {
81+
self.get_request_with_query(path, params).await
82+
}
83+
84+
pub async fn post<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> Response {
85+
let response = reqwest::Client::new()
86+
.post(self.base_url(path).clone())
87+
.json(&form)
4688
.send()
4789
.await
48-
.unwrap()
90+
.unwrap();
91+
Response::from(response).await
4992
}
5093

51-
pub async fn get_request(&self, path: &str) -> Response {
52-
get(&self.base_url(path), None).await
94+
async fn delete(&self, path: &str) -> Response {
95+
let response = match &self.connection_info.token {
96+
Some(token) => reqwest::Client::new()
97+
.delete(self.base_url(path).clone())
98+
.bearer_auth(token)
99+
.send()
100+
.await
101+
.unwrap(),
102+
None => reqwest::Client::new()
103+
.delete(self.base_url(path).clone())
104+
.send()
105+
.await
106+
.unwrap(),
107+
};
108+
Response::from(response).await
53109
}
54-
*/
55110

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

tests/e2e/connection_info.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,28 @@ pub fn anonymous_connection(bind_address: &str) -> ConnectionInfo {
22
ConnectionInfo::anonymous(bind_address)
33
}
44

5+
pub fn authenticated_connection(bind_address: &str, token: &str) -> ConnectionInfo {
6+
ConnectionInfo::new(bind_address, token)
7+
}
8+
59
#[derive(Clone)]
610
pub struct ConnectionInfo {
711
pub bind_address: String,
12+
pub token: Option<String>,
813
}
914

1015
impl ConnectionInfo {
16+
pub fn new(bind_address: &str, token: &str) -> Self {
17+
Self {
18+
bind_address: bind_address.to_string(),
19+
token: Some(token.to_string()),
20+
}
21+
}
22+
1123
pub fn anonymous(bind_address: &str) -> Self {
1224
Self {
1325
bind_address: bind_address.to_string(),
26+
token: None,
1427
}
1528
}
1629
}

tests/e2e/routes/about.rs renamed to tests/e2e/contexts/about.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::e2e::asserts::{assert_response_title, assert_text_ok};
2-
use crate::e2e::env::TestEnv;
2+
use crate::e2e::environment::TestEnv;
33

44
#[tokio::test]
55
#[cfg_attr(not(feature = "e2e-tests"), ignore)]

0 commit comments

Comments
 (0)