Skip to content

Commit

Permalink
tests: [torrust#120] E2E tests for torrent routes
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Apr 27, 2023
1 parent ea36618 commit e3ed344
Show file tree
Hide file tree
Showing 19 changed files with 1,021 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ jobs:
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest
- name: Test Coverage
run: cargo llvm-cov nextest
run: cargo llvm-cov nextest
- name: E2E Tests
run: ./docker/bin/run-e2e-tests.sh
2 changes: 1 addition & 1 deletion bin/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fi
# Generate storage directory if it does not exist
mkdir -p "./storage/database"

# Generate the sqlite database for the index baclend if it does not exist
# Generate the sqlite database for the index backend if it does not exist
if ! [ -f "./storage/database/data.db" ]; then
# todo: it should get the path from config.toml and only do it when we use sqlite
touch ./storage/database/data.db
Expand Down
26 changes: 26 additions & 0 deletions docker/bin/e2e-env-reset.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

# Delete the SQLite databases and recreate them.

./docker/bin/e2e-env-down.sh

rm -f ./storage/database/torrust_index_backend_e2e_testing.db
rm -f ./storage/database/torrust_tracker_e2e_testing.db

# Generate storage directory if it does not exist
mkdir -p "./storage/database"

# Generate the sqlite database for the index backend if it does not exist
if ! [ -f "./storage/database/torrust_index_backend_e2e_testing.db" ]; then
# todo: it should get the path from config.toml and only do it when we use sqlite
touch ./storage/database/torrust_index_backend_e2e_testing.db
echo ";" | sqlite3 ./storage/database/torrust_index_backend_e2e_testing.db
fi

# Generate the sqlite database for the tracker if it does not exist
if ! [ -f "./storage/database/torrust_tracker_e2e_testing.db" ]; then
touch ./storage/database/torrust_tracker_e2e_testing.db
echo ";" | sqlite3 ./storage/database/torrust_tracker_e2e_testing.db
fi

./docker/bin/e2e-env-up.sh
3 changes: 3 additions & 0 deletions docker/bin/run-e2e-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ wait_for_container_to_be_healthy() {
return 1
}

# Install tool to create torrent files
cargo install imdl

cp .env.local .env
./bin/install.sh

Expand Down
4 changes: 4 additions & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ hasher
Hasher
httpseeds
imagoodboy
imdl
infohash
jsonwebtoken
leechers
Leechers
Expand All @@ -41,6 +43,8 @@ strftime
sublicensable
sublist
subpoints
tempdir
tempfile
torrust
Torrust
upgrader
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::e2e::response::Response;
use crate::e2e::responses::TextResponse;

// Text responses

pub fn assert_response_title(response: &Response, title: &str) {
pub fn assert_response_title(response: &TextResponse, title: &str) {
let title_element = format!("<title>{title}</title>");

assert!(
Expand All @@ -11,14 +11,14 @@ pub fn assert_response_title(response: &Response, title: &str) {
);
}

pub fn assert_text_ok(response: &Response) {
pub fn assert_text_ok(response: &TextResponse) {
assert_eq!(response.status, 200);
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) {
pub fn _assert_text_bad_request(response: &TextResponse) {
assert_eq!(response.status, 400);
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "text/plain; charset=utf-8");
Expand All @@ -27,7 +27,7 @@ pub fn _assert_text_bad_request(response: &Response) {

// JSON responses

pub fn assert_json_ok(response: &Response) {
pub fn assert_json_ok(response: &TextResponse) {
assert_eq!(response.status, 200);
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "application/json");
Expand Down
144 changes: 120 additions & 24 deletions tests/e2e/client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use reqwest::multipart;
use serde::Serialize;

use super::contexts::category::{AddCategoryForm, DeleteCategoryForm};
use super::contexts::settings::UpdateSettingsForm;
use super::contexts::torrent::requests::{TorrentId, UpdateTorrentFrom};
use super::contexts::user::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username};
use super::responses::{self, BinaryResponse};
use crate::e2e::connection_info::ConnectionInfo;
use crate::e2e::http::{Query, ReqwestQuery};
use crate::e2e::response::Response;
use crate::e2e::responses::TextResponse;

/// API Client
pub struct Client {
Expand All @@ -21,71 +24,99 @@ impl Client {

// Context: about

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

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

// Context: category

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

pub async fn add_category(&self, add_category_form: AddCategoryForm) -> Response {
pub async fn add_category(&self, add_category_form: AddCategoryForm) -> TextResponse {
self.http_client.post("category", &add_category_form).await
}

pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> Response {
pub async fn delete_category(&self, delete_category_form: DeleteCategoryForm) -> TextResponse {
self.http_client.delete_with_body("category", &delete_category_form).await
}

// Context: root

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

// Context: settings

pub async fn get_public_settings(&self) -> Response {
pub async fn get_public_settings(&self) -> TextResponse {
self.http_client.get("settings/public", Query::empty()).await
}

pub async fn get_site_name(&self) -> Response {
pub async fn get_site_name(&self) -> TextResponse {
self.http_client.get("settings/name", Query::empty()).await
}

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

pub async fn update_settings(&self, update_settings_form: UpdateSettingsForm) -> Response {
pub async fn update_settings(&self, update_settings_form: UpdateSettingsForm) -> TextResponse {
self.http_client.post("settings", &update_settings_form).await
}

// Context: torrent

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

pub async fn get_torrent(&self, id: TorrentId) -> TextResponse {
self.http_client.get(&format!("torrent/{id}"), Query::empty()).await
}

pub async fn delete_torrent(&self, id: TorrentId) -> TextResponse {
self.http_client.delete(&format!("torrent/{id}")).await
}

pub async fn update_torrent(&self, id: TorrentId, update_torrent_form: UpdateTorrentFrom) -> TextResponse {
self.http_client.put(&format!("torrent/{id}"), &update_torrent_form).await
}

pub async fn upload_torrent(&self, form: multipart::Form) -> TextResponse {
self.http_client.post_multipart("torrent/upload", form).await
}

pub async fn download_torrent(&self, id: TorrentId) -> responses::BinaryResponse {
self.http_client
.get_binary(&format!("torrent/download/{id}"), Query::empty())
.await
}

// Context: user

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

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

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

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

pub async fn ban_user(&self, username: Username) -> Response {
pub async fn ban_user(&self, username: Username) -> TextResponse {
self.http_client.delete(&format!("user/ban/{}", &username.value)).await
}
}
Expand All @@ -104,7 +135,30 @@ impl Http {
}
}

pub async fn get(&self, path: &str, params: Query) -> Response {
pub async fn get(&self, path: &str, params: Query) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::builder()
.build()
.unwrap()
.get(self.base_url(path).clone())
.query(&ReqwestQuery::from(params))
.bearer_auth(token)
.send()
.await
.unwrap(),
None => reqwest::Client::builder()
.build()
.unwrap()
.get(self.base_url(path).clone())
.query(&ReqwestQuery::from(params))
.send()
.await
.unwrap(),
};
TextResponse::from(response).await
}

pub async fn get_binary(&self, path: &str, params: Query) -> BinaryResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::builder()
.build()
Expand All @@ -124,10 +178,10 @@ impl Http {
.await
.unwrap(),
};
Response::from(response).await
BinaryResponse::from(response).await
}

pub async fn post<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> Response {
pub async fn post<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::new()
.post(self.base_url(path).clone())
Expand All @@ -143,10 +197,52 @@ impl Http {
.await
.unwrap(),
};
Response::from(response).await
TextResponse::from(response).await
}

pub async fn post_multipart(&self, path: &str, form: multipart::Form) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::builder()
.build()
.unwrap()
.post(self.base_url(path).clone())
.multipart(form)
.bearer_auth(token)
.send()
.await
.unwrap(),
None => reqwest::Client::builder()
.build()
.unwrap()
.post(self.base_url(path).clone())
.multipart(form)
.send()
.await
.unwrap(),
};
TextResponse::from(response).await
}

pub async fn put<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::new()
.put(self.base_url(path).clone())
.bearer_auth(token)
.json(&form)
.send()
.await
.unwrap(),
None => reqwest::Client::new()
.put(self.base_url(path).clone())
.json(&form)
.send()
.await
.unwrap(),
};
TextResponse::from(response).await
}

async fn delete(&self, path: &str) -> Response {
async fn delete(&self, path: &str) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::new()
.delete(self.base_url(path).clone())
Expand All @@ -160,10 +256,10 @@ impl Http {
.await
.unwrap(),
};
Response::from(response).await
TextResponse::from(response).await
}

async fn delete_with_body<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> Response {
async fn delete_with_body<T: Serialize + ?Sized>(&self, path: &str, form: &T) -> TextResponse {
let response = match &self.connection_info.token {
Some(token) => reqwest::Client::new()
.delete(self.base_url(path).clone())
Expand All @@ -179,7 +275,7 @@ impl Http {
.await
.unwrap(),
};
Response::from(response).await
TextResponse::from(response).await
}

fn base_url(&self, path: &str) -> String {
Expand Down
12 changes: 10 additions & 2 deletions tests/e2e/contexts/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,17 @@ pub mod fixtures {
use super::AddCategoryForm;
use crate::e2e::contexts::user::fixtures::logged_in_admin;
use crate::e2e::environment::TestEnv;
use crate::e2e::response::Response;
use crate::e2e::responses::TextResponse;

pub async fn add_category(category_name: &str) -> Response {
pub fn software_predefined_category_name() -> String {
"software".to_string()
}

pub fn software_predefined_category_id() -> i64 {
5
}

pub async fn add_category(category_name: &str) -> TextResponse {
let logged_in_admin = logged_in_admin().await;
let client = TestEnv::default().authenticated_client(&logged_in_admin.token);

Expand Down
1 change: 1 addition & 0 deletions tests/e2e/contexts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod about;
pub mod category;
pub mod root;
pub mod settings;
pub mod torrent;
pub mod user;
Loading

0 comments on commit e3ed344

Please sign in to comment.