Skip to content

Commit

Permalink
test(api): [torrust#187] add tests for new 'tag' context
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jun 9, 2023
1 parent e766b4c commit b97698a
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/databases/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ impl Database for Mysql {
.bind(name)
.fetch_one(&self.pool)
.await
.map_err(|err| database::Error::TagNotFound)
.map_err(|_| database::Error::TagNotFound)
}

async fn get_tags(&self) -> Result<Vec<TorrentTag>, database::Error> {
Expand Down
2 changes: 1 addition & 1 deletion src/databases/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ impl Database for Sqlite {
.bind(name)
.fetch_one(&self.pool)
.await
.map_err(|err| database::Error::TagNotFound)
.map_err(|_| database::Error::TagNotFound)
}

async fn get_tags(&self) -> Result<Vec<TorrentTag>, database::Error> {
Expand Down
17 changes: 17 additions & 0 deletions tests/common/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::Serialize;
use super::connection_info::ConnectionInfo;
use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm};
use super::contexts::settings::form::UpdateSettings;
use super::contexts::tag::forms::{AddTagForm, DeleteTagForm};
use super::contexts::torrent::forms::UpdateTorrentFrom;
use super::contexts::torrent::requests::InfoHash;
use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username};
Expand Down Expand Up @@ -67,6 +68,22 @@ impl Client {
self.http_client.delete_with_body("/category", &delete_category_form).await
}

// Context: tag

pub async fn get_tags(&self) -> TextResponse {
// code-review: some endpoint are using plural
// (for instance, `get_categories`) and some singular.
self.http_client.get("/tags", Query::empty()).await
}

pub async fn add_tag(&self, add_tag_form: AddTagForm) -> TextResponse {
self.http_client.post("/tag", &add_tag_form).await
}

pub async fn delete_tag(&self, delete_tag_form: DeleteTagForm) -> TextResponse {
self.http_client.delete_with_body("/tag", &delete_tag_form).await
}

// Context: root

pub async fn root(&self) -> TextResponse {
Expand Down
1 change: 1 addition & 0 deletions tests/common/contexts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod about;
pub mod category;
pub mod root;
pub mod settings;
pub mod tag;
pub mod torrent;
pub mod user;
10 changes: 10 additions & 0 deletions tests/common/contexts/tag/fixtures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use rand::Rng;

pub fn random_tag_name() -> String {
format!("category name {}", random_id())
}

fn random_id() -> u64 {
let mut rng = rand::thread_rng();
rng.gen_range(0..1_000_000)
}
11 changes: 11 additions & 0 deletions tests/common/contexts/tag/forms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::Serialize;

#[derive(Serialize)]
pub struct AddTagForm {
pub name: String,
}

#[derive(Serialize)]
pub struct DeleteTagForm {
pub tag_id: i64,
}
3 changes: 3 additions & 0 deletions tests/common/contexts/tag/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod fixtures;
pub mod forms;
pub mod responses;
28 changes: 28 additions & 0 deletions tests/common/contexts/tag/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use serde::Deserialize;

#[derive(Deserialize)]
pub struct AddedTagResponse {
pub data: String,
}

#[derive(Deserialize)]
pub struct DeletedTagResponse {
pub data: i64, // tag_id
}

#[derive(Deserialize, Debug)]
pub struct ListResponse {
pub data: Vec<ListItem>,
}

impl ListResponse {
pub fn find_tag_id(&self, tag_name: &str) -> i64 {
self.data.iter().find(|tag| tag.name == tag_name).unwrap().tag_id
}
}

#[derive(Deserialize, Debug, PartialEq)]
pub struct ListItem {
pub tag_id: i64,
pub name: String,
}
1 change: 1 addition & 0 deletions tests/e2e/contexts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod about;
pub mod category;
pub mod root;
pub mod settings;
pub mod tag;
pub mod torrent;
pub mod user;
183 changes: 183 additions & 0 deletions tests/e2e/contexts/tag/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//! API contract for `tag` context.
use torrust_index_backend::web::api;

use crate::common::asserts::assert_json_ok;
use crate::common::client::Client;
use crate::common::contexts::tag::fixtures::random_tag_name;
use crate::common::contexts::tag::forms::{AddTagForm, DeleteTagForm};
use crate::common::contexts::tag::responses::{AddedTagResponse, DeletedTagResponse, ListResponse};
use crate::e2e::contexts::tag::steps::{add_random_tag, add_tag};
use crate::e2e::contexts::user::steps::{new_logged_in_admin, new_logged_in_user};
use crate::e2e::environment::TestEnv;

#[tokio::test]
async fn it_should_return_an_empty_tag_list_when_there_are_no_tags() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

let response = client.get_tags().await;

assert_json_ok(&response);
}

#[tokio::test]
async fn it_should_return_a_tag_list() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

// Add a tag
let tag_name = random_tag_name();
let response = add_tag(&tag_name, &env).await;
assert_eq!(response.status, 200);

let response = client.get_tags().await;

let res: ListResponse = serde_json::from_str(&response.body).unwrap();

// There should be at least the tag we added.
// Since this is an E2E test that could be executed in a shred env,
// there might be more tags.
assert!(!res.data.is_empty());
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "application/json");
}
assert_eq!(response.status, 200);
}

#[tokio::test]
async fn it_should_not_allow_adding_a_new_tag_to_unauthenticated_users() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

let response = client
.add_tag(AddTagForm {
name: "TAG NAME".to_string(),
})
.await;

assert_eq!(response.status, 401);
}

#[tokio::test]
async fn it_should_not_allow_adding_a_new_tag_to_non_admins() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

let logged_non_admin = new_logged_in_user(&env).await;

let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_non_admin.token);

let response = client
.add_tag(AddTagForm {
name: "TAG NAME".to_string(),
})
.await;

assert_eq!(response.status, 403);
}

#[tokio::test]
async fn it_should_allow_admins_to_add_new_tags() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

let logged_in_admin = new_logged_in_admin(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);

let tag_name = random_tag_name();

let response = client
.add_tag(AddTagForm {
name: tag_name.to_string(),
})
.await;

let res: AddedTagResponse = serde_json::from_str(&response.body).unwrap();

assert_eq!(res.data, tag_name);
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "application/json");
}
assert_eq!(response.status, 200);
}

#[tokio::test]
async fn it_should_allow_adding_duplicated_tags() {
// code-review: is this an intended behavior?

let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

// Add a tag
let random_tag_name = random_tag_name();
let response = add_tag(&random_tag_name, &env).await;
assert_eq!(response.status, 200);

// Try to add the same tag again
let response = add_tag(&random_tag_name, &env).await;
assert_eq!(response.status, 200);
}

#[tokio::test]
async fn it_should_allow_adding_a_tag_with_an_empty_name() {
// code-review: is this an intended behavior?

let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

let empty_tag_name = String::new();
let response = add_tag(&empty_tag_name, &env).await;
assert_eq!(response.status, 200);
}

#[tokio::test]
async fn it_should_allow_admins_to_delete_tags() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

let logged_in_admin = new_logged_in_admin(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);

let (tag_id, _tag_name) = add_random_tag(&env).await;

let response = client.delete_tag(DeleteTagForm { tag_id }).await;

let res: DeletedTagResponse = serde_json::from_str(&response.body).unwrap();

assert_eq!(res.data, tag_id);
if let Some(content_type) = &response.content_type {
assert_eq!(content_type, "application/json");
}
assert_eq!(response.status, 200);
}

#[tokio::test]
async fn it_should_not_allow_non_admins_to_delete_tags() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;

let logged_in_non_admin = new_logged_in_user(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_non_admin.token);

let (tag_id, _tag_name) = add_random_tag(&env).await;

let response = client.delete_tag(DeleteTagForm { tag_id }).await;

assert_eq!(response.status, 403);
}

#[tokio::test]
async fn it_should_not_allow_guests_to_delete_tags() {
let mut env = TestEnv::new();
env.start(api::Implementation::ActixWeb).await;
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

let (tag_id, _tag_name) = add_random_tag(&env).await;

let response = client.delete_tag(DeleteTagForm { tag_id }).await;

assert_eq!(response.status, 401);
}
2 changes: 2 additions & 0 deletions tests/e2e/contexts/tag/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod contract;
pub mod steps;
39 changes: 39 additions & 0 deletions tests/e2e/contexts/tag/steps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::common::client::Client;
use crate::common::contexts::tag::fixtures::random_tag_name;
use crate::common::contexts::tag::forms::AddTagForm;
use crate::common::contexts::tag::responses::ListResponse;
use crate::common::responses::TextResponse;
use crate::e2e::contexts::user::steps::new_logged_in_admin;
use crate::e2e::environment::TestEnv;

pub async fn add_random_tag(env: &TestEnv) -> (i64, String) {
let tag_name = random_tag_name();

add_tag(&tag_name, env).await;

let tag_id = get_tag_id(&tag_name, env).await;

(tag_id, tag_name)
}

pub async fn add_tag(tag_name: &str, env: &TestEnv) -> TextResponse {
let logged_in_admin = new_logged_in_admin(env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);

client
.add_tag(AddTagForm {
name: tag_name.to_string(),
})
.await
}

pub async fn get_tag_id(tag_name: &str, env: &TestEnv) -> i64 {
let logged_in_admin = new_logged_in_admin(env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);

let response = client.get_tags().await;

let res: ListResponse = serde_json::from_str(&response.body).unwrap();

res.find_tag_id(tag_name)
}

0 comments on commit b97698a

Please sign in to comment.