Skip to content

Commit

Permalink
test(api): [#143] add tests for database failure
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jan 11, 2023
1 parent 1515753 commit e1ed929
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 20 deletions.
5 changes: 5 additions & 0 deletions src/databases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ pub trait Database: Sync + Send {
/// Will return `Error` if unable to create own tables.
fn create_database_tables(&self) -> Result<(), Error>;

/// # Errors
///
/// Will return `Err` if unable to drop tables.
fn drop_database_tables(&self) -> Result<(), Error>;

async fn load_persistent_torrents(&self) -> Result<Vec<(InfoHash, u32)>, Error>;

async fn load_keys(&self) -> Result<Vec<auth::Key>, Error>;
Expand Down
24 changes: 24 additions & 0 deletions src/databases/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ impl Database for Mysql {
Ok(())
}

fn drop_database_tables(&self) -> Result<(), Error> {
let drop_whitelist_table = "
DROP TABLE `whitelist`;"
.to_string();

let drop_torrents_table = "
DROP TABLE `torrents`;"
.to_string();

let drop_keys_table = "
DROP TABLE `keys`;"
.to_string();

let mut conn = self.pool.get().map_err(|_| Error::DatabaseError)?;

conn.query_drop(&drop_whitelist_table)
.expect("Could not drop `whitelist` table.");
conn.query_drop(&drop_torrents_table)
.expect("Could not drop `torrents` table.");
conn.query_drop(&drop_keys_table).expect("Could not drop `keys` table.");

Ok(())
}

async fn load_persistent_torrents(&self) -> Result<Vec<(InfoHash, u32)>, Error> {
let mut conn = self.pool.get().map_err(|_| Error::DatabaseError)?;

Expand Down
22 changes: 22 additions & 0 deletions src/databases/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ impl Database for Sqlite {
.map(|_| ())
}

fn drop_database_tables(&self) -> Result<(), Error> {
let drop_whitelist_table = "
DROP TABLE whitelist;"
.to_string();

let drop_torrents_table = "
DROP TABLE torrents;"
.to_string();

let drop_keys_table = "
DROP TABLE keys;"
.to_string();

let conn = self.pool.get().map_err(|_| Error::DatabaseError)?;

conn.execute(&drop_whitelist_table, [])
.and_then(|_| conn.execute(&drop_torrents_table, []))
.and_then(|_| conn.execute(&drop_keys_table, []))
.map_err(|_| Error::InvalidQuery)
.map(|_| ())
}

async fn load_persistent_torrents(&self) -> Result<Vec<(InfoHash, u32)>, Error> {
let conn = self.pool.get().map_err(|_| Error::DatabaseError)?;

Expand Down
6 changes: 4 additions & 2 deletions src/tracker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct Tracker {
torrents: RwLock<std::collections::BTreeMap<InfoHash, torrent::Entry>>,
stats_event_sender: Option<Box<dyn statistics::EventSender>>,
stats_repository: statistics::Repo,
database: Box<dyn Database>,
pub database: Box<dyn Database>,
}

#[derive(Debug, PartialEq, Default)]
Expand Down Expand Up @@ -130,7 +130,9 @@ impl Tracker {

/// It adds a torrent to the whitelist if it has not been whitelisted previously
async fn add_torrent_to_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
if self.database.is_info_hash_whitelisted(info_hash).await.unwrap() {
let is_whitelisted = self.database.is_info_hash_whitelisted(info_hash).await?;

if is_whitelisted {
return Ok(());
}

Expand Down
49 changes: 36 additions & 13 deletions tests/api/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
use reqwest::Response;

pub async fn assert_torrent_not_known(response: Response) {
assert_eq!(response.status(), 200);
assert_eq!(response.headers().get("content-type").unwrap(), "application/json");
assert_eq!(response.text().await.unwrap(), "\"torrent not known\"");
}

pub async fn assert_token_not_valid(response: Response) {
assert_eq!(response.status(), 500);
assert_eq!(response.headers().get("content-type").unwrap(), "text/plain; charset=utf-8");
assert_eq!(
response.text().await.unwrap(),
"Unhandled rejection: Err { reason: \"token not valid\" }"
);
assert_unhandled_rejection(response, "token not valid").await;
}

pub async fn assert_unauthorized(response: Response) {
assert_unhandled_rejection(response, "unauthorized").await;
}

pub async fn assert_failed_to_remove_torrent_from_whitelist(response: Response) {
assert_unhandled_rejection(response, "failed to remove torrent from whitelist").await;
}

pub async fn assert_failed_to_whitelist_torrent(response: Response) {
assert_unhandled_rejection(response, "failed to whitelist torrent").await;
}

pub async fn assert_failed_to_generate_key(response: Response) {
assert_unhandled_rejection(response, "failed to generate key").await;
}

pub async fn assert_failed_to_delete_key(response: Response) {
assert_unhandled_rejection(response, "failed to delete key").await;
}

pub async fn assert_failed_to_reload_whitelist(response: Response) {
assert_unhandled_rejection(response, "failed to reload whitelist").await;
}

pub async fn assert_failed_to_reload_keys(response: Response) {
assert_unhandled_rejection(response, "failed to reload keys").await;
}

async fn assert_unhandled_rejection(response: Response, reason: &str) {
assert_eq!(response.status(), 500);
assert_eq!(response.headers().get("content-type").unwrap(), "text/plain; charset=utf-8");
assert_eq!(
response.text().await.unwrap(),
"Unhandled rejection: Err { reason: \"unauthorized\" }"
format!("Unhandled rejection: Err {{ reason: \"{reason}\" }}")
);
}

pub async fn assert_torrent_not_known(response: Response) {
assert_eq!(response.status(), 200);
assert_eq!(response.headers().get("content-type").unwrap(), "application/json");
assert_eq!(response.text().await.unwrap(), "\"torrent not known\"");
}
11 changes: 11 additions & 0 deletions tests/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::sync::Arc;

use torrust_tracker::tracker::Tracker;

pub mod asserts;
pub mod client;
pub mod connection_info;
Expand All @@ -8,3 +12,10 @@ pub enum Version {
Warp,
Axum,
}

/// It forces a database error by dropping all tables.
/// That makes any query fail.
/// code-review: alternatively we could inject a database mock in the future.
pub fn force_database_error(tracker: &Arc<Tracker>) {
tracker.database.drop_database_tables().unwrap();
}
121 changes: 116 additions & 5 deletions tests/tracker_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ mod tracker_api {
Keys:
POST /api/key/:seconds_valid
GET /api/keys/reload
DELETE /api/key/:key
Key command:
GET /api/keys/reload
*/

mod for_stats_resources {
Expand Down Expand Up @@ -291,11 +293,14 @@ mod tracker_api {

use torrust_tracker::protocol::info_hash::InfoHash;

use crate::api::asserts::{assert_token_not_valid, assert_unauthorized};
use crate::api::asserts::{
assert_failed_to_reload_whitelist, assert_failed_to_remove_torrent_from_whitelist,
assert_failed_to_whitelist_torrent, assert_token_not_valid, assert_unauthorized,
};
use crate::api::client::Client;
use crate::api::connection_info::{connection_with_invalid_token, connection_with_no_token};
use crate::api::server::start_default_api;
use crate::api::Version;
use crate::api::{force_database_error, Version};

#[tokio::test]
async fn should_allow_whitelisting_a_torrent() {
Expand Down Expand Up @@ -350,6 +355,38 @@ mod tracker_api {
assert_unauthorized(response).await;
}

#[tokio::test]
async fn should_return_an_error_when_the_torrent_cannot_be_whitelisted() {
let api_server = start_default_api(&Version::Warp).await;

let info_hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned();

force_database_error(&api_server.tracker);

let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.whitelist_a_torrent(&info_hash)
.await;

assert_failed_to_whitelist_torrent(response).await;
}

#[tokio::test]
async fn should_return_an_error_when_the_torrent_cannot_be_removed_from_the_whitelist() {
let api_server = start_default_api(&Version::Warp).await;

let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned();
let info_hash = InfoHash::from_str(&hash).unwrap();
api_server.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap();

force_database_error(&api_server.tracker);

let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.remove_torrent_from_whitelist(&hash)
.await;

assert_failed_to_remove_torrent_from_whitelist(response).await;
}

#[tokio::test]
async fn should_allow_removing_a_torrent_from_the_whitelist() {
let api_server = start_default_api(&Version::Warp).await;
Expand Down Expand Up @@ -412,6 +449,23 @@ mod tracker_api {
);
*/
}

#[tokio::test]
async fn should_return_an_error_when_the_whitelist_cannot_be_reloaded_from_the_database() {
let api_server = start_default_api(&Version::Warp).await;

let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned();
let info_hash = InfoHash::from_str(&hash).unwrap();
api_server.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap();

force_database_error(&api_server.tracker);

let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.reload_whitelist()
.await;

assert_failed_to_reload_whitelist(response).await;
}
}

mod for_key_resources {
Expand All @@ -420,11 +474,14 @@ mod tracker_api {
use torrust_tracker::api::resource::auth_key::AuthKey;
use torrust_tracker::tracker::auth::Key;

use crate::api::asserts::{assert_token_not_valid, assert_unauthorized};
use crate::api::asserts::{
assert_failed_to_delete_key, assert_failed_to_generate_key, assert_failed_to_reload_keys, assert_token_not_valid,
assert_unauthorized,
};
use crate::api::client::Client;
use crate::api::connection_info::{connection_with_invalid_token, connection_with_no_token};
use crate::api::server::start_default_api;
use crate::api::Version;
use crate::api::{force_database_error, Version};

#[tokio::test]
async fn should_allow_generating_a_new_auth_key() {
Expand Down Expand Up @@ -463,6 +520,20 @@ mod tracker_api {
assert_unauthorized(response).await;
}

#[tokio::test]
async fn should_return_an_error_when_the_auth_key_cannot_be_generated() {
let api_server = start_default_api(&Version::Warp).await;

force_database_error(&api_server.tracker);

let seconds_valid = 60;
let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.generate_auth_key(seconds_valid)
.await;

assert_failed_to_generate_key(response).await;
}

#[tokio::test]
async fn should_allow_deleting_an_auth_key() {
let api_server = start_default_api(&Version::Warp).await;
Expand All @@ -482,6 +553,26 @@ mod tracker_api {
assert_eq!(response.text().await.unwrap(), "{\"status\":\"ok\"}");
}

#[tokio::test]
async fn should_return_an_error_when_the_auth_key_cannot_be_deleted() {
let api_server = start_default_api(&Version::Warp).await;

let seconds_valid = 60;
let auth_key = api_server
.tracker
.generate_auth_key(Duration::from_secs(seconds_valid))
.await
.unwrap();

force_database_error(&api_server.tracker);

let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.delete_auth_key(&auth_key.key)
.await;

assert_failed_to_delete_key(response).await;
}

#[tokio::test]
async fn should_not_allow_deleting_an_auth_key_for_unauthenticated_users() {
let api_server = start_default_api(&Version::Warp).await;
Expand Down Expand Up @@ -533,6 +624,26 @@ mod tracker_api {
assert_eq!(response.status(), 200);
}

#[tokio::test]
async fn should_return_an_error_when_keys_cannot_be_reloaded() {
let api_server = start_default_api(&Version::Warp).await;

let seconds_valid = 60;
api_server
.tracker
.generate_auth_key(Duration::from_secs(seconds_valid))
.await
.unwrap();

force_database_error(&api_server.tracker);

let response = Client::new(api_server.get_connection_info(), &Version::Warp)
.reload_keys()
.await;

assert_failed_to_reload_keys(response).await;
}

#[tokio::test]
async fn should_not_allow_reloading_keys_for_unauthenticated_users() {
let api_server = start_default_api(&Version::Warp).await;
Expand Down

0 comments on commit e1ed929

Please sign in to comment.