diff --git a/crates/db/src/core/definitions.rs b/crates/db/src/core/definitions.rs index 26ab1597..c3a2a03b 100644 --- a/crates/db/src/core/definitions.rs +++ b/crates/db/src/core/definitions.rs @@ -21,12 +21,14 @@ pub trait QuestsDatabase: Send + Sync + CloneDatabase { async fn deactivate_quest(&self, id: &str) -> DBResult; async fn get_quest(&self, id: &str) -> DBResult; async fn get_active_quests(&self, offset: i64, limit: i64) -> DBResult>; - async fn get_quests_by_creator_id( + async fn count_active_quests(&self) -> DBResult; + async fn get_quests_by_creator_address( &self, - creator_id: &str, + creator_address: &str, offset: i64, limit: i64, ) -> DBResult>; + async fn count_quests_by_creator_address(&self, creator_address: &str) -> DBResult; async fn is_active_quest(&self, quest_id: &str) -> DBResult; async fn has_active_quest_instance(&self, user_address: &str, quest_id: &str) -> DBResult; @@ -59,10 +61,12 @@ pub trait QuestsDatabase: Send + Sync + CloneDatabase { offset: i64, limit: i64, ) -> DBResult>; + async fn count_active_quest_instances_by_quest_id(&self, quest_id: &str) -> DBResult; async fn add_event(&self, event: &AddEvent, quest_instance_id: &str) -> DBResult<()>; async fn get_events(&self, quest_instance_id: &str) -> DBResult>; - async fn remove_events(&self, quest_instance_id: &str) -> DBResult<()>; + async fn remove_events_from_quest_instance(&self, quest_instance_id: &str) -> DBResult<()>; + async fn remove_event(&self, event_id: &str) -> DBResult<()>; async fn add_reward_hook_to_quest( &self, diff --git a/crates/db/src/core/errors.rs b/crates/db/src/core/errors.rs index 0669b855..1b8aafd7 100644 --- a/crates/db/src/core/errors.rs +++ b/crates/db/src/core/errors.rs @@ -58,6 +58,9 @@ pub enum DBError { #[error("Unable to retrieve all instances related to Quest ID {0}: {1}")] GetQuestInstancesByQuestIdFailed(String, BoxDynError), + #[error("Unable to retrieve all active instances related to Quest ID {0}: {1}")] + GetActiveQuestInstancesByQuestIdFailed(String, BoxDynError), + #[error("Unable to check if a quest instance is still active: {0}")] GetActiveQuestInstanceFailed(BoxDynError), @@ -87,6 +90,15 @@ pub enum DBError { #[error("Unable to check if a quest instance is completed: {0}")] IsCompletedInstanceFailed(BoxDynError), + #[error("Unable to count active quest instances: {0}")] + UnableToCountActiveQuestInstances(BoxDynError), + + #[error("Unable to count active quests: {0}")] + UnableToCountActiveQuests(BoxDynError), + + #[error("Unable to check quest creator: {0}")] + UnableToCheckQuestCreator(BoxDynError), + #[error("Row has incorrect data: {0}")] RowCorrupted(BoxDynError), diff --git a/crates/db/src/core/tests.rs b/crates/db/src/core/tests.rs index c24bd9bb..a5167ce8 100644 --- a/crates/db/src/core/tests.rs +++ b/crates/db/src/core/tests.rs @@ -9,6 +9,10 @@ pub async fn quest_database_works(db: &DB, quest: CreateQues assert!(db.ping().await); let quest_id = db.create_quest(&quest, "0xA").await.unwrap(); + let count_active_quest = db.count_active_quests().await.unwrap(); + + assert_eq!(count_active_quest, 1); + let is_creator = db.is_quest_creator(&quest_id, "0xA").await.unwrap(); assert!(is_creator); @@ -89,10 +93,17 @@ pub async fn quest_database_works(db: &DB, quest: CreateQues assert!(!db.can_activate_quest(&quest_id).await.unwrap()); // creators quests should be ONE because query returns current versions (activated and deactivated) and not old versions - let quests_by_creator = db.get_quests_by_creator_id("0xA", 0, 50).await.unwrap(); + let quests_by_creator = db + .get_quests_by_creator_address("0xA", 0, 50) + .await + .unwrap(); assert_eq!(quests_by_creator.len(), 1); assert_eq!(quests_by_creator.first().unwrap().id, new_quest_id); assert!(quests_by_creator.first().unwrap().active); + + let count_quest_by_creator = db.count_quests_by_creator_address("0xA").await.unwrap(); + assert_eq!(count_quest_by_creator, 1); + let create_deactivated_quest = CreateQuest { name: "DEACTIVATED_CREATOR_QUEST", description: quest.description, @@ -100,13 +111,17 @@ pub async fn quest_database_works(db: &DB, quest: CreateQues image_url: quest.image_url, reward: None, }; + let deactivated_quest = db .create_quest(&create_deactivated_quest, "0xA") .await .unwrap(); db.deactivate_quest(&deactivated_quest).await.unwrap(); // creators quests should be TWO because query returns current versions (activated and deactivated) and not old versions - let quests_by_creator = db.get_quests_by_creator_id("0xA", 0, 50).await.unwrap(); + let quests_by_creator = db + .get_quests_by_creator_address("0xA", 0, 50) + .await + .unwrap(); assert_eq!(quests_by_creator.len(), 2); // order by desc assert_eq!(quests_by_creator.first().unwrap().id, deactivated_quest); @@ -145,6 +160,12 @@ pub async fn quest_database_works(db: &DB, quest: CreateQues assert_eq!(quest_instances.len(), 1); + let count_quest_instances = db + .count_active_quest_instances_by_quest_id(&quest_id) + .await + .unwrap(); + assert_eq!(count_quest_instances, 1); + let instances_by_quest_id = db .get_all_quest_instances_by_quest_id(&quest_id) .await @@ -281,9 +302,32 @@ pub async fn quest_database_works(db: &DB, quest: CreateQues assert!(is_completed); // test remove events - db.remove_events(&new_quest_instance_id).await.unwrap(); + db.remove_events_from_quest_instance(&new_quest_instance_id) + .await + .unwrap(); + let events = db.get_events(&new_quest_instance_id).await.unwrap(); + assert_eq!(events.len(), 0); + + let event_id = uuid::Uuid::new_v4().to_string(); + db.add_event( + &AddEvent { + id: event_id.clone(), + user_address: "0xD", + event: vec![0], + }, + &new_quest_instance_id, + ) + .await + .unwrap(); + + let events = db.get_events(&new_quest_instance_id).await.unwrap(); + assert_eq!(events.len(), 1); + + db.remove_event(&event_id).await.unwrap(); + let events = db.get_events(&new_quest_instance_id).await.unwrap(); assert_eq!(events.len(), 0); + // test remove from completed quest instances db.remove_instance_from_completed_instances(&new_quest_instance_id) .await diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs index 2542e995..9a5f69e2 100644 --- a/crates/db/src/lib.rs +++ b/crates/db/src/lib.rs @@ -134,7 +134,22 @@ impl QuestsDatabase for Database { Ok(quests) } - async fn get_quests_by_creator_id( + async fn count_active_quests(&self) -> DBResult { + let count: i64 = sqlx::query_scalar( + " + SELECT count(id) FROM quests + WHERE id NOT IN + (SELECT quest_id as id FROM deactivated_quests) + ", + ) + .fetch_one(&self.pool) + .await + .map_err(|err| DBError::UnableToCountActiveQuestInstances(Box::new(err)))?; + + Ok(count) + } + + async fn get_quests_by_creator_address( &self, creator_address: &str, offset: i64, @@ -196,6 +211,22 @@ impl QuestsDatabase for Database { Ok(quests) } + async fn count_quests_by_creator_address(&self, creator_address: &str) -> DBResult { + let count: i64 = sqlx::query_scalar( + "SELECT count(q.id) + FROM quests q + LEFT JOIN deactivated_quests dq ON q.id = dq.quest_id + LEFT JOIN quest_updates uq ON q.id = uq.previous_quest_id + WHERE q.creator_address = $1 AND uq.id IS NULL", + ) + .bind(creator_address) + .fetch_one(&self.pool) + .await + .map_err(|err| DBError::UnableToCountActiveQuestInstances(Box::new(err)))?; + + Ok(count) + } + async fn create_quest(&self, quest: &CreateQuest, creator_address: &str) -> DBResult { let quest_id = if let Some(reward) = &quest.reward { let mut tx = self @@ -526,7 +557,7 @@ impl QuestsDatabase for Database { Ok(events) } - async fn remove_events(&self, quest_instance_id: &str) -> DBResult<()> { + async fn remove_events_from_quest_instance(&self, quest_instance_id: &str) -> DBResult<()> { sqlx::query("DELETE FROM events WHERE quest_instance_id = $1") .bind(parse_str_to_uuid(quest_instance_id)?) .execute(&self.pool) @@ -536,6 +567,20 @@ impl QuestsDatabase for Database { Ok(()) } + async fn remove_event(&self, event_id: &str) -> DBResult<()> { + let query_result = sqlx::query("DELETE FROM events WHERE id = $1") + .bind(parse_str_to_uuid(event_id)?) + .execute(&self.pool) + .await + .map_err(|err| DBError::GetQuestEventsFailed(Box::new(err)))?; + + if query_result.rows_affected() == 0 { + return Err(DBError::RowNotFound); + } + + Ok(()) + } + async fn add_reward_hook_to_quest( &self, quest_id: &str, @@ -619,7 +664,7 @@ impl QuestsDatabase for Database { .fetch_all(&self.pool) .await .map_err(|err| { - DBError::GetQuestInstancesByQuestIdFailed(quest_id.to_string(), Box::new(err)) + DBError::GetActiveQuestInstancesByQuestIdFailed(quest_id.to_string(), Box::new(err)) })?; let result: Result, _> = @@ -628,6 +673,20 @@ impl QuestsDatabase for Database { result.map_err(|err| DBError::RowCorrupted(Box::new(err))) } + async fn count_active_quest_instances_by_quest_id(&self, quest_id: &str) -> DBResult { + let count: i64 = sqlx::query_scalar( + "SELECT count(id) FROM quest_instances + WHERE quest_id = $1 + AND id NOT IN (SELECT quest_instance_id as id FROM abandoned_quest_instances)", + ) + .bind(parse_str_to_uuid(quest_id)?) + .fetch_one(&self.pool) + .await + .map_err(|err| DBError::UnableToCountActiveQuestInstances(Box::new(err)))?; + + Ok(count) + } + async fn is_quest_creator(&self, quest_id: &str, creator_address: &str) -> DBResult { let quest_exists: bool = sqlx::query_scalar( " @@ -639,7 +698,7 @@ impl QuestsDatabase for Database { .bind(creator_address) .fetch_one(&self.pool) .await - .map_err(|err| DBError::CanActivateQuestFailed(Box::new(err)))?; + .map_err(|err| DBError::UnableToCheckQuestCreator(Box::new(err)))?; Ok(quest_exists) } diff --git a/crates/server/src/api/mod.rs b/crates/server/src/api/mod.rs index 00db3fd0..af47c140 100644 --- a/crates/server/src/api/mod.rs +++ b/crates/server/src/api/mod.rs @@ -39,8 +39,8 @@ pub async fn run_server( pub fn get_app_router( config: &Data, - db: &Data, - redis: &Data, + database: &Data, + events_queue: &Data, metrics_collector: &Data, ) -> App< impl ServiceFactory< @@ -57,8 +57,8 @@ pub fn get_app_router( .app_data(json_extractor_config()) .app_data(path_extractor_config()) .app_data(config.clone()) - .app_data(db.clone()) - .app_data(redis.clone()) + .app_data(database.clone()) + .app_data(events_queue.clone()) .app_data(metrics_collector.clone()) .wrap(dcl_http_prom_metrics::metrics()) .wrap(middlewares::metrics_token(&config.wkc_metrics_bearer_token)) diff --git a/crates/server/src/api/routes/api_doc.rs b/crates/server/src/api/routes/api_doc.rs index b60f7e0a..31822e54 100644 --- a/crates/server/src/api/routes/api_doc.rs +++ b/crates/server/src/api/routes/api_doc.rs @@ -30,7 +30,10 @@ use utoipa_redoc::Servable; quests::get_quest_instances, creators::get_quests_by_creator_id, quest_instances::reset_quest_instance, - quest_instances::get_quest_instance_state + quest_instances::get_quest_instance_state, + quest_instances::add_event_to_instance, + quest_instances::get_quest_instance, + quest_instances::remove_event_from_instance, ), components( schemas( @@ -53,6 +56,7 @@ use utoipa_redoc::Servable; quests_protocol::definitions::Connection, quests_protocol::definitions::QuestState, quests_protocol::definitions::StepContent, + quests_protocol::definitions::EventRequest, quests_protocol::definitions::Task, quests_db::core::definitions::QuestReward, quests_db::core::definitions::QuestRewardHook, @@ -61,7 +65,10 @@ use utoipa_redoc::Servable; quests_db::core::definitions::QuestInstance, quest_instances::state::GetInstanceStateResponse, quests::get_instances::GetQuestInstancesResponse, - quests::get_instances::GetQuestInstancesQuery + quests::get_instances::GetQuestInstancesQuery, + quest_instances::add_event::AddEventToInstancePayload, + quest_instances::add_event::AddEventToInstanceResponse, + quest_instances::get::GetQuestInstanceResponse, ) ), tags( diff --git a/crates/server/src/api/routes/creators/get_quests_by_creator_id.rs b/crates/server/src/api/routes/creators/get_quests_by_creator_id.rs index f396c68a..328085f7 100644 --- a/crates/server/src/api/routes/creators/get_quests_by_creator_id.rs +++ b/crates/server/src/api/routes/creators/get_quests_by_creator_id.rs @@ -17,6 +17,7 @@ pub struct GetQuestsQuery { #[derive(Serialize, Deserialize, ToSchema)] pub struct GetCreatorQuestsResponse { pub quests: Vec, + pub total: i64, } /// Get quests by creator id @@ -47,23 +48,32 @@ pub async fn get_quests_by_creator_id( }; match db - .get_quests_by_creator_id( + .get_quests_by_creator_address( &user_address.to_ascii_lowercase(), query.offset.unwrap_or(0), query.limit.unwrap_or(50), ) .await { - Ok(stored_quests) => { - let mut quests = vec![]; - for stored_quest in stored_quests { - match stored_quest.to_quest(is_owner) { - Ok(quest) => quests.push(quest), - Err(err) => return HttpResponse::from_error(err), + Ok(stored_quests) => match db + .count_quests_by_creator_address(&user_address.to_ascii_lowercase()) + .await + { + Ok(total) => { + let mut quests = vec![]; + for stored_quest in stored_quests { + match stored_quest.to_quest(is_owner) { + Ok(quest) => quests.push(quest), + Err(err) => return HttpResponse::from_error(err), + } } + HttpResponse::Ok().json(GetCreatorQuestsResponse { quests, total }) } - HttpResponse::Ok().json(GetCreatorQuestsResponse { quests }) - } + Err(err) => { + log::error!("Error counting quests: {:?}", err); + HttpResponse::from_error(QuestError::from(err)) + } + }, Err(err) => { log::error!("Error getting quests: {:?}", err); HttpResponse::from_error(QuestError::from(err)) diff --git a/crates/server/src/api/routes/quest_instances/add_event.rs b/crates/server/src/api/routes/quest_instances/add_event.rs new file mode 100644 index 00000000..c919e744 --- /dev/null +++ b/crates/server/src/api/routes/quest_instances/add_event.rs @@ -0,0 +1,68 @@ +use crate::{ + api::middlewares::RequiredAuthUser, + domain::{events::add_event_controller, quests::QuestError}, +}; +use actix_web::{post, web, HttpResponse}; +use quests_db::{core::definitions::QuestsDatabase, Database}; +use quests_message_broker::messages_queue::RedisMessagesQueue; +use quests_protocol::definitions::EventRequest; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct AddEventToInstancePayload { + pub event: EventRequest, +} + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct AddEventToInstanceResponse { + pub accepted: bool, +} + +/// Get Quest Instance's state. Allowed for the Quest Creator +#[utoipa::path( + params( + ("quest_instance" = String, description = "Quest Instance UUID") + ), + responses( + (status = 200, description = "Event enqueue result", body = AddEventToInstanceResponse), + (status = 401, description = "Unauthorized"), + (status = 403, description = "Forbidden"), + (status = 404, description = "Quest Instance not found"), + (status = 500, description = "Internal Server Error") + ) +)] +#[post("/instances/{quest_instance}/events")] +pub async fn add_event_to_instance( + data: web::Data, + events_queue: web::Data, + quest_instance: web::Path, + event: web::Json, + auth_user: RequiredAuthUser, +) -> HttpResponse { + let db = data.into_inner(); + + let RequiredAuthUser { address } = auth_user; + + match db.get_quest_instance(&quest_instance).await { + Ok(instance) => match db.is_quest_creator(&instance.quest_id, &address).await { + Ok(is_creator) if !is_creator => HttpResponse::from_error(QuestError::NotQuestCreator), + Ok(_) => { + match add_event_controller( + events_queue.into_inner(), + &instance.user_address, + event.event.to_owned(), + ) + .await + { + Ok(_) => HttpResponse::Ok().json(AddEventToInstanceResponse { accepted: true }), + Err(_) => { + HttpResponse::Ok().json(AddEventToInstanceResponse { accepted: false }) + } + } + } + Err(err) => HttpResponse::from_error(QuestError::from(err)), + }, + Err(err) => HttpResponse::from_error(QuestError::from(err)), + } +} diff --git a/crates/server/src/api/routes/quest_instances/get.rs b/crates/server/src/api/routes/quest_instances/get.rs new file mode 100644 index 00000000..d4dac3de --- /dev/null +++ b/crates/server/src/api/routes/quest_instances/get.rs @@ -0,0 +1,56 @@ +use crate::{api::middlewares::RequiredAuthUser, domain::quests::QuestError}; +use actix_web::{get, web, HttpResponse}; +use quests_db::{ + core::definitions::{QuestInstance, QuestsDatabase}, + Database, +}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Deserialize, Serialize, ToSchema)] +pub struct GetQuestInstanceResponse { + pub instance: QuestInstance, +} + +/// Get a specific quest instance. Only the Quest Creator is allowed to see the Quest Instances +#[utoipa::path( + params( + ("quest_instance_id" = String, description = "Quest Instance UUID") + ), + responses( + (status = 200, description = "Quest Instance", body = GetQuestInstanceResponse), + (status = 401, description = "Unathorized"), + (status = 403, description = "Forbidden"), + (status = 404, description = "Not Found"), + (status = 500, description = "Internal Server Error") + ) +)] +#[get("/instances/{quest_instance_id}")] +pub async fn get_quest_instance( + db: web::Data, + quest_instance_id: web::Path, + auth_user: RequiredAuthUser, +) -> HttpResponse { + let db = db.into_inner(); + let quest_instance_id = quest_instance_id.into_inner(); + + let RequiredAuthUser { address } = auth_user; + + match db.get_quest_instance(&quest_instance_id).await { + Ok(instance) => match db.is_quest_creator(&instance.quest_id, &address).await { + Ok(is_creator) if !is_creator => HttpResponse::from_error(QuestError::NotQuestCreator), + Ok(_) => HttpResponse::Ok().json(GetQuestInstanceResponse { instance }), + Err(err) => { + log::error!( + "error on checking if {address} is quest creator of {}", + instance.quest_id + ); + HttpResponse::from_error(QuestError::from(err)) + } + }, + Err(err) => { + log::error!("error on getting quest instance {quest_instance_id} : {err}"); + HttpResponse::from_error(QuestError::from(err)) + } + } +} diff --git a/crates/server/src/api/routes/quest_instances/mod.rs b/crates/server/src/api/routes/quest_instances/mod.rs index a0a66b3b..665b36f0 100644 --- a/crates/server/src/api/routes/quest_instances/mod.rs +++ b/crates/server/src/api/routes/quest_instances/mod.rs @@ -1,7 +1,13 @@ +pub mod add_event; +pub mod get; +pub mod remove_event; pub mod reset; pub mod state; use actix_web::Scope; +pub use add_event::*; +pub use get::*; +pub use remove_event::*; pub use reset::*; pub use state::*; @@ -9,4 +15,7 @@ pub fn services(api_scope: Scope) -> Scope { api_scope .service(reset_quest_instance) .service(get_quest_instance_state) + .service(get_quest_instance) + .service(add_event_to_instance) + .service(remove_event_from_instance) } diff --git a/crates/server/src/api/routes/quest_instances/remove_event.rs b/crates/server/src/api/routes/quest_instances/remove_event.rs new file mode 100644 index 00000000..288673fc --- /dev/null +++ b/crates/server/src/api/routes/quest_instances/remove_event.rs @@ -0,0 +1,48 @@ +use crate::{api::middlewares::RequiredAuthUser, domain::quests::QuestError}; +use actix_web::{delete, web, HttpResponse}; +use quests_db::{core::definitions::QuestsDatabase, Database}; + +/// Get Quest Instance's state. Allowed for the Quest Creator +#[utoipa::path( +params( + ("quest_instance" = String, description = "Quest Instance UUID") +), +responses( + (status = 204, description = "Event removed"), + (status = 401, description = "Unauthorized"), + (status = 403, description = "Forbidden"), + (status = 404, description = "Quest Instance or Event not found"), + (status = 500, description = "Internal Server Error") +) +)] +#[delete("/instances/{quest_instance}/events/{event_id}")] +pub async fn remove_event_from_instance( + data: web::Data, + path: web::Path<(String, String)>, + auth_user: RequiredAuthUser, +) -> HttpResponse { + let db = data.into_inner(); + + let (quest_instance_id, event_id) = path.into_inner(); + + let RequiredAuthUser { address } = auth_user; + + match db.get_quest_instance(&quest_instance_id).await { + Ok(instance) => match db.is_quest_creator(&instance.quest_id, &address).await { + Ok(is_creator) if !is_creator => HttpResponse::from_error(QuestError::NotQuestCreator), + Ok(_) => match db.remove_event(&event_id).await { + // we remove it from completed instance in case it's already completed + Ok(_) => match db + .remove_instance_from_completed_instances(&instance.id) + .await + { + Ok(_) => HttpResponse::NoContent().finish(), + Err(err) => HttpResponse::from_error(QuestError::from(err)), + }, + Err(err) => HttpResponse::from_error(QuestError::from(err)), + }, + Err(err) => HttpResponse::from_error(QuestError::from(err)), + }, + Err(err) => HttpResponse::from_error(QuestError::from(err)), + } +} diff --git a/crates/server/src/api/routes/quest_instances/reset.rs b/crates/server/src/api/routes/quest_instances/reset.rs index 928edd7d..f7b7cf98 100644 --- a/crates/server/src/api/routes/quest_instances/reset.rs +++ b/crates/server/src/api/routes/quest_instances/reset.rs @@ -45,10 +45,12 @@ async fn reset_quest_instance_controller( } // remove events to reset quest instance state - db.remove_events(quest_instance_id).await.map_err(|err| { - let err: QuestError = err.into(); - err - })?; + db.remove_events_from_quest_instance(quest_instance_id) + .await + .map_err(|err| { + let err: QuestError = err.into(); + err + })?; db.remove_instance_from_completed_instances(quest_instance_id) .await diff --git a/crates/server/src/api/routes/quests/activate_quest.rs b/crates/server/src/api/routes/quests/activate_quest.rs index 0c4a7256..5e3f2ccf 100644 --- a/crates/server/src/api/routes/quests/activate_quest.rs +++ b/crates/server/src/api/routes/quests/activate_quest.rs @@ -7,7 +7,7 @@ use quests_db::{core::definitions::QuestsDatabase, Database}; /// Activates a quest by its ID #[utoipa::path( params( - ("quest_id" = String, description = "ID of the quest to activate") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 202, description = "Quest activated"), diff --git a/crates/server/src/api/routes/quests/delete_quest.rs b/crates/server/src/api/routes/quests/delete_quest.rs index bb2589a5..5efb66b6 100644 --- a/crates/server/src/api/routes/quests/delete_quest.rs +++ b/crates/server/src/api/routes/quests/delete_quest.rs @@ -7,7 +7,7 @@ use quests_db::{core::definitions::QuestsDatabase, Database}; /// Deactivate a quest #[utoipa::path( params( - ("quest_id" = String, description = "ID of the quest to deactivate") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 202, description = "Quest deactivated"), diff --git a/crates/server/src/api/routes/quests/get_instances.rs b/crates/server/src/api/routes/quests/get_instances.rs index 1b0641c0..a429be1d 100644 --- a/crates/server/src/api/routes/quests/get_instances.rs +++ b/crates/server/src/api/routes/quests/get_instances.rs @@ -16,16 +16,17 @@ pub struct GetQuestInstancesQuery { #[derive(Deserialize, Serialize, ToSchema)] pub struct GetQuestInstancesResponse { pub instances: Vec, + pub total: i64, } /// Get all quest instances. Only the Quest Creator is allowed to see the Quest Instances #[utoipa::path( params( ("query" = GetQuestsQuery, Query, description = "Offset and limit params"), - ("quest_id" = String, description = "Quest ID") + ("quest_id" = String, description = "Quest UUID") ), responses( - (status = 200, description = "Quest's Instances", body = GetQuestInstacesResponse), + (status = 200, description = "Quest's Instances", body = GetQuestInstancesResponse), (status = 401, description = "Unathorized"), (status = 403, description = "Forbidden"), (status = 500, description = "Internal Server Error") @@ -53,7 +54,15 @@ pub async fn get_quest_instances( ) .await { - Ok(instances) => HttpResponse::Ok().json(GetQuestInstancesResponse { instances }), + Ok(instances) => match db.count_active_quest_instances_by_quest_id(&quest_id).await { + Ok(total) => { + HttpResponse::Ok().json(GetQuestInstancesResponse { instances, total }) + } + Err(err) => { + log::error!("error on counting quest instances {err} for {quest_id}"); + HttpResponse::from_error(QuestError::from(err)) + } + }, Err(err) => { log::error!("error on getting quest instances {err} for {quest_id}"); HttpResponse::from_error(QuestError::from(err)) diff --git a/crates/server/src/api/routes/quests/get_quest.rs b/crates/server/src/api/routes/quests/get_quest.rs index 5738ba1f..94dfb5b5 100644 --- a/crates/server/src/api/routes/quests/get_quest.rs +++ b/crates/server/src/api/routes/quests/get_quest.rs @@ -15,7 +15,7 @@ pub struct GetQuestResponse { /// Returns the quest definition if the user is the creator of the quest (authentication required) #[utoipa::path( params( - ("quest_id" = String, description = "Quest ID") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 200, description = "Quest definition", body = GetQuestResponse), diff --git a/crates/server/src/api/routes/quests/get_quest_reward.rs b/crates/server/src/api/routes/quests/get_quest_reward.rs index 176cc44d..94ab0514 100644 --- a/crates/server/src/api/routes/quests/get_quest_reward.rs +++ b/crates/server/src/api/routes/quests/get_quest_reward.rs @@ -31,7 +31,7 @@ pub struct GetQuestRewardsParams { /// Returns the quest rewards #[utoipa::path( params( - ("quest_id" = String, description = "ID of the Quest") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 200, description = "Quest Rewards", body = GetQuestRewardResponse), diff --git a/crates/server/src/api/routes/quests/get_quest_stats.rs b/crates/server/src/api/routes/quests/get_quest_stats.rs index 8f84df4b..d2550745 100644 --- a/crates/server/src/api/routes/quests/get_quest_stats.rs +++ b/crates/server/src/api/routes/quests/get_quest_stats.rs @@ -21,7 +21,7 @@ pub struct GetQuestStatsResponse { /// Get a quest stats #[utoipa::path( params( - ("quest_id" = String, description = "Quest ID") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 200, description = "Quest Stats", body = GetQuestStatsResponse), diff --git a/crates/server/src/api/routes/quests/get_quest_updates.rs b/crates/server/src/api/routes/quests/get_quest_updates.rs index 4120f227..0626f5dc 100644 --- a/crates/server/src/api/routes/quests/get_quest_updates.rs +++ b/crates/server/src/api/routes/quests/get_quest_updates.rs @@ -14,7 +14,7 @@ pub struct GetQuestUpdatesResponse { /// Returns the IDs of the old quests #[utoipa::path( params( - ("quest_id" = String, description = "Quest ID") + ("quest_id" = String, description = "Quest UUID") ), responses( (status = 200, description = "IDs of the old quests", body = GetQuestUpdatesResponse), diff --git a/crates/server/src/api/routes/quests/get_quests.rs b/crates/server/src/api/routes/quests/get_quests.rs index 6f22c5d1..e8528b24 100644 --- a/crates/server/src/api/routes/quests/get_quests.rs +++ b/crates/server/src/api/routes/quests/get_quests.rs @@ -16,6 +16,7 @@ pub struct GetQuestsQuery { #[derive(Serialize, Deserialize, ToSchema)] pub struct GetQuestsResponse { pub quests: Vec, + pub total: i64, } /// Get quests. @@ -40,7 +41,7 @@ pub async fn get_quests( let db = db.into_inner(); match get_quests_controller(db, query.offset.unwrap_or(0), query.limit.unwrap_or(50)).await { - Ok(quests) => HttpResponse::Ok().json(GetQuestsResponse { quests }), + Ok((quests, total)) => HttpResponse::Ok().json(GetQuestsResponse { quests, total }), Err(err) => HttpResponse::from_error(err), } } @@ -49,18 +50,21 @@ async fn get_quests_controller( db: Arc, offset: i64, limit: i64, -) -> Result, QuestError> { +) -> Result<(Vec, i64), QuestError> { match db.get_active_quests(offset, limit).await { - Ok(stored_quests) => { - let mut quests = vec![]; - for stored_quest in stored_quests { - match stored_quest.to_quest(false) { - Ok(quest) => quests.push(quest), - Err(err) => return Err(err), + Ok(stored_quests) => match db.count_active_quests().await { + Ok(total) => { + let mut quests = vec![]; + for stored_quest in stored_quests { + match stored_quest.to_quest(false) { + Ok(quest) => quests.push(quest), + Err(err) => return Err(err), + } } + Ok((quests, total)) } - Ok(quests) - } + Err(err) => Err(err.into()), + }, Err(err) => Err(err.into()), } } diff --git a/crates/server/src/api/routes/quests/update_quest.rs b/crates/server/src/api/routes/quests/update_quest.rs index 85a2e054..fec0c6cf 100644 --- a/crates/server/src/api/routes/quests/update_quest.rs +++ b/crates/server/src/api/routes/quests/update_quest.rs @@ -26,7 +26,7 @@ pub struct UpdateQuestResponse { #[utoipa::path( request_body = UpdateQuestRequest, params( - ("quest_id" = String, Path, description = "Quest ID") + ("quest_id" = String, Path, description = "Quest UUID") ), responses( (status = 200, description = "Quest updated", body = UpdateQuestResponse), diff --git a/crates/server/tests/add_event_to_instance.rs b/crates/server/tests/add_event_to_instance.rs new file mode 100644 index 00000000..235481ef --- /dev/null +++ b/crates/server/tests/add_event_to_instance.rs @@ -0,0 +1,145 @@ +mod common; +use actix_web::http::StatusCode; +use actix_web::test::{call_service, init_service, read_body_json, TestRequest}; +pub use common::*; +use quests_db::core::definitions::{CreateQuest, QuestsDatabase}; +use quests_db::create_quests_db_component; +use quests_protocol::definitions::*; +use quests_server::api::routes::quest_instances::{ + AddEventToInstancePayload, AddEventToInstanceResponse, +}; + +#[actix_web::test] +async fn add_event_to_instance_should_be_200() { + let config = get_configuration(Some(5)).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!("/api/instances/{}/events", quest_instance_id); + + let body = AddEventToInstancePayload { + event: EventRequest { + action: Some(Action { + r#type: "CUSTOM".to_string(), + parameters: quest + .definition + .as_ref() + .unwrap() + .steps + .first() + .unwrap() + .tasks + .first() + .unwrap() + .action_items + .first() + .unwrap() + .parameters + .clone(), + }), + }, + }; + + let headers = get_signed_headers(create_test_identity(), "post", &path, ""); + + let req = TestRequest::post() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .set_json(body) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::OK); + + let json: AddEventToInstanceResponse = read_body_json(response).await; + assert!(json.accepted) +} + +#[actix_web::test] +async fn add_event_to_instance_should_be_403() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1ba5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!("/api/instances/{}/events", quest_instance_id); + + let headers = get_signed_headers(create_test_identity(), "post", &path, ""); + + let body = AddEventToInstancePayload { + event: EventRequest { + action: Some(Action { + r#type: "CUSTOM".to_string(), + parameters: quest + .definition + .as_ref() + .unwrap() + .steps + .first() + .unwrap() + .tasks + .first() + .unwrap() + .action_items + .first() + .unwrap() + .parameters + .clone(), + }), + }, + }; + + let req = TestRequest::post() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .set_json(body) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::FORBIDDEN); +} diff --git a/crates/server/tests/common/mod.rs b/crates/server/tests/common/mod.rs index 5c820a81..bafbc8d5 100644 --- a/crates/server/tests/common/mod.rs +++ b/crates/server/tests/common/mod.rs @@ -17,10 +17,13 @@ use quests_server::api::get_app_router; use quests_server::configuration::Config; use quests_system::QUESTS_EVENTS_QUEUE_NAME; -pub async fn get_configuration() -> Config { +pub async fn get_configuration(redis_switch_db: Option) -> Config { let mut config = Config::new().expect("Couldn't read the configuration file"); let new_url = create_test_db(&config.database_url).await; config.database_url = new_url; + if let Some(db_num) = redis_switch_db { + config.redis_url = format!("{}/{}", config.redis_url, db_num) + } config } @@ -42,7 +45,7 @@ pub async fn build_app( let redis = Redis::new(&config.redis_url) .await - .expect("> tests > failed to initialize redis"); + .unwrap_or_else(|_| panic!("> tests > failed to initialize redis {}", config.redis_url)); let events_queue = RedisMessagesQueue::new(redis.into(), QUESTS_EVENTS_QUEUE_NAME); get_app_router( diff --git a/crates/server/tests/create_quest.rs b/crates/server/tests/create_quest.rs index 9fdbcab3..66483803 100644 --- a/crates/server/tests/create_quest.rs +++ b/crates/server/tests/create_quest.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; #[actix_web::test] async fn create_quest_should_be_200_without_reward() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -73,7 +73,7 @@ async fn create_quest_should_be_200_without_reward() { #[actix_web::test] async fn create_quest_should_be_200_with_reward() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -143,7 +143,7 @@ async fn create_quest_should_be_200_with_reward() { #[actix_web::test] async fn create_quest_should_be_400_quest_validation_error_missing_definition() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let quest_definition = CreateQuestRequest { name: "QUEST-1".to_string(), @@ -185,7 +185,7 @@ async fn create_quest_should_be_400_quest_validation_error_missing_definition() #[actix_web::test] async fn create_quest_should_be_400_quest_validation_error_rewards_webhook() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let Quest { name, @@ -240,7 +240,7 @@ async fn create_quest_should_be_400_quest_validation_error_rewards_webhook() { #[actix_web::test] async fn create_quest_should_be_400_quest_validation_error_rewards_items() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let Quest { name, @@ -356,7 +356,7 @@ async fn create_quest_should_be_400_quest_validation_error_rewards_items() { #[actix_web::test] async fn create_quest_should_be_401() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let quest_definition = CreateQuestRequest { name: "QUEST-1".to_string(), diff --git a/crates/server/tests/deactivate_quest.rs b/crates/server/tests/deactivate_quest.rs index d92d530e..5ed9d5ed 100644 --- a/crates/server/tests/deactivate_quest.rs +++ b/crates/server/tests/deactivate_quest.rs @@ -10,7 +10,7 @@ use quests_server::api::routes::ErrorResponse; #[actix_web::test] async fn deactivate_quest_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -62,7 +62,7 @@ async fn deactivate_quest_should_be_200() { #[actix_web::test] async fn delete_quest_should_be_400() { - let config = get_configuration().await; + let config = get_configuration(None).await; let headers = get_signed_headers(create_test_identity(), "delete", "/api/quests/1aab", "{}"); @@ -86,7 +86,7 @@ async fn delete_quest_should_be_400() { #[actix_web::test] async fn delete_quest_should_be_401() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let req = TestRequest::delete().uri("/api/quests/1aab").to_request(); @@ -97,7 +97,7 @@ async fn delete_quest_should_be_401() { #[actix_web::test] async fn deactivate_quest_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/get_instance_state.rs b/crates/server/tests/get_instance_state.rs index ff5a340d..c2d9ab10 100644 --- a/crates/server/tests/get_instance_state.rs +++ b/crates/server/tests/get_instance_state.rs @@ -9,7 +9,7 @@ use quests_server::api::routes::quest_instances::GetInstanceStateResponse; #[actix_web::test] async fn get_instance_state_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -55,7 +55,7 @@ async fn get_instance_state_should_be_200() { #[actix_web::test] async fn get_instance_state_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/get_quest.rs b/crates/server/tests/get_quest.rs index c52142a9..1c2a8768 100644 --- a/crates/server/tests/get_quest.rs +++ b/crates/server/tests/get_quest.rs @@ -10,7 +10,7 @@ use quests_server::api::routes::ErrorResponse; #[actix_web::test] async fn get_quest_with_defintiions_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -80,7 +80,7 @@ async fn get_quest_with_defintiions_should_be_200() { #[actix_web::test] async fn get_quest_without_defintiions_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -120,7 +120,7 @@ async fn get_quest_without_defintiions_should_be_200() { #[actix_web::test] async fn get_quest_should_be_400() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let req = TestRequest::get().uri("/api/quests/1aaa").to_request(); @@ -134,7 +134,7 @@ async fn get_quest_should_be_400() { #[actix_web::test] async fn get_quest_should_be_404() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let id = uuid::Uuid::new_v4().to_string(); diff --git a/crates/server/tests/get_quest_instance.rs b/crates/server/tests/get_quest_instance.rs new file mode 100644 index 00000000..e9cf338b --- /dev/null +++ b/crates/server/tests/get_quest_instance.rs @@ -0,0 +1,124 @@ +mod common; +use actix_web::http::StatusCode; +use actix_web::test::{call_service, init_service, read_body_json, TestRequest}; +pub use common::*; +use quests_db::core::definitions::{CreateQuest, QuestsDatabase}; +use quests_db::create_quests_db_component; +use quests_protocol::definitions::*; +use quests_server::api::routes::quest_instances::GetQuestInstanceResponse; +use uuid::Uuid; + +#[actix_web::test] +async fn get_quest_instance_should_be_200() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!("/api/instances/{}", quest_instance_id); + + let headers = get_signed_headers(create_test_identity(), "get", &path, ""); + + let req = TestRequest::get() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::OK); + + let json: GetQuestInstanceResponse = read_body_json(response).await; + assert_eq!(json.instance.id, quest_instance_id) +} + +#[actix_web::test] +async fn get_quest_instance_should_be_403() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1ba5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!("/api/instances/{}", quest_instance_id); + + let headers = get_signed_headers(create_test_identity(), "get", &path, ""); + + let req = TestRequest::get() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::FORBIDDEN); +} + +#[actix_web::test] +async fn get_quest_instance_should_be_404() { + let config = get_configuration(None).await; + create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + + let random_uuid = Uuid::new_v4().to_string(); + + let path = format!("/api/instances/{}", random_uuid); + + let headers = get_signed_headers(create_test_identity(), "get", &path, ""); + + let req = TestRequest::get() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::NOT_FOUND); +} diff --git a/crates/server/tests/get_quest_instances.rs b/crates/server/tests/get_quest_instances.rs index ca2e67c0..6c92b2f3 100644 --- a/crates/server/tests/get_quest_instances.rs +++ b/crates/server/tests/get_quest_instances.rs @@ -9,7 +9,7 @@ use quests_server::api::routes::quests::GetQuestInstancesResponse; #[actix_web::test] async fn get_quest_instances_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -51,12 +51,13 @@ async fn get_quest_instances_should_be_200() { let json: GetQuestInstancesResponse = read_body_json(response).await; assert_eq!(json.instances.len(), 1); + assert_eq!(json.total, 1); assert_eq!(json.instances.first().unwrap().id, quest_instance_id); } #[actix_web::test] async fn get_quest_instances_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/get_quest_rewards.rs b/crates/server/tests/get_quest_rewards.rs index 4f149bb6..e53ccc81 100644 --- a/crates/server/tests/get_quest_rewards.rs +++ b/crates/server/tests/get_quest_rewards.rs @@ -17,7 +17,7 @@ use std::collections::HashMap; #[actix_web::test] async fn get_quest_rewards_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -69,7 +69,7 @@ async fn get_quest_rewards_should_be_200() { #[actix_web::test] async fn quest_has_no_rewards() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/get_quest_stats.rs b/crates/server/tests/get_quest_stats.rs index a932f45e..93b12f1d 100644 --- a/crates/server/tests/get_quest_stats.rs +++ b/crates/server/tests/get_quest_stats.rs @@ -11,7 +11,7 @@ use quests_server::api::routes::quests::GetQuestStatsResponse; #[actix_web::test] async fn get_quest_stats_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -74,7 +74,7 @@ async fn get_quest_stats_should_be_200() { #[actix_web::test] async fn get_quest_stats_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -126,7 +126,7 @@ async fn get_quest_stats_should_be_403() { #[actix_web::test] async fn get_quest_stats_should_be_401() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/get_quests.rs b/crates/server/tests/get_quests.rs index b070f84c..1653a988 100644 --- a/crates/server/tests/get_quests.rs +++ b/crates/server/tests/get_quests.rs @@ -10,7 +10,7 @@ use quests_server::api::routes::{quests::GetQuestsResponse, ErrorResponse}; #[actix_web::test] async fn get_quests_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let db = create_quests_db_component(&config.database_url, true) .await @@ -39,12 +39,13 @@ async fn get_quests_should_be_200() { let body: GetQuestsResponse = read_body_json(response).await; assert_eq!(body.quests.len(), 1); + assert_eq!(body.total, 1); assert_eq!(body.quests[0].name, quest_definition.name) } #[actix_web::test] async fn get_quests_should_be_400() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let req = TestRequest::get() .uri("/api/quests?offset=0aa") diff --git a/crates/server/tests/remove_event_from_instance.rs b/crates/server/tests/remove_event_from_instance.rs new file mode 100644 index 00000000..5237a5b5 --- /dev/null +++ b/crates/server/tests/remove_event_from_instance.rs @@ -0,0 +1,174 @@ +mod common; +use actix_web::http::StatusCode; +use actix_web::test::{call_service, init_service, TestRequest}; +pub use common::*; +use quests_db::core::definitions::{AddEvent, CreateQuest, QuestsDatabase}; +use quests_db::create_quests_db_component; +use quests_protocol::definitions::*; +use uuid::Uuid; + +#[actix_web::test] +async fn remove_event_from_instance_should_be_200() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let event_id = Uuid::new_v4().to_string(); + db.add_event( + &AddEvent { + id: event_id.clone(), + user_address: "0xA", + event: vec![], + }, + &quest_instance_id, + ) + .await + .unwrap(); + + let path = format!("/api/instances/{}/events/{}", quest_instance_id, event_id); + + let headers = get_signed_headers(create_test_identity(), "delete", &path, ""); + + let req = TestRequest::delete() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::NO_CONTENT); +} + +#[actix_web::test] +async fn remove_event_from_instance_should_be_403() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1ba5") + .await + .unwrap(); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!( + "/api/instances/{}/events/{}", + quest_instance_id, + Uuid::new_v4() + ); + + let headers = get_signed_headers(create_test_identity(), "delete", &path, ""); + + let req = TestRequest::delete() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::FORBIDDEN); +} + +#[actix_web::test] +async fn remove_event_from_instance_should_be_404() { + let config = get_configuration(None).await; + let db = create_quests_db_component(&config.database_url, true) + .await + .unwrap(); + + let app = init_service(build_app(&config).await).await; + let quest = quest_samples::grab_some_apples(); + + let create_quest = CreateQuest { + name: &quest.name, + description: &quest.description, + image_url: &quest.image_url, + definition: quest.definition.as_ref().unwrap().encode_to_vec(), + reward: None, + }; + + let id = db + .create_quest(&create_quest, "0x7949f9f239d1a0816ce5eb364a1f588ae9cc1bf5") + .await + .unwrap(); + + let path = format!( + "/api/instances/{}/events/{}", + Uuid::new_v4(), + Uuid::new_v4() + ); + + let headers = get_signed_headers(create_test_identity(), "delete", &path, ""); + + let req = TestRequest::delete() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::NOT_FOUND); + + let quest_instance_id = db.start_quest(&id, "0xA").await.unwrap(); + + let path = format!( + "/api/instances/{}/events/{}", + quest_instance_id, + Uuid::new_v4() + ); + + let headers = get_signed_headers(create_test_identity(), "delete", &path, ""); + + let req = TestRequest::delete() + .uri(&path) + .append_header(headers[0].clone()) + .append_header(headers[1].clone()) + .append_header(headers[2].clone()) + .append_header(headers[3].clone()) + .append_header(headers[4].clone()) + .to_request(); + + let response = call_service(&app, req).await; + assert_eq!(response.status().as_u16(), StatusCode::NOT_FOUND); +} diff --git a/crates/server/tests/reset_quest_instance.rs b/crates/server/tests/reset_quest_instance.rs index c021f1b7..15d2e7b5 100644 --- a/crates/server/tests/reset_quest_instance.rs +++ b/crates/server/tests/reset_quest_instance.rs @@ -8,7 +8,7 @@ use quests_protocol::definitions::*; #[actix_web::test] async fn reset_quest_instance_should_be_204() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -72,7 +72,7 @@ async fn reset_quest_instance_should_be_204() { #[actix_web::test] async fn reset_quest_instance_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/server/tests/update_quest.rs b/crates/server/tests/update_quest.rs index 7f238b1b..610cc24d 100644 --- a/crates/server/tests/update_quest.rs +++ b/crates/server/tests/update_quest.rs @@ -11,7 +11,7 @@ use quests_server::api::routes::ErrorResponse; #[actix_web::test] async fn update_quest_should_be_200() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); @@ -124,7 +124,7 @@ async fn update_quest_should_be_200() { #[actix_web::test] async fn update_quest_should_be_400_uuid_bad_format() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let quest_definition = CreateQuestRequest { name: "QUEST-1".to_string(), @@ -210,7 +210,7 @@ async fn update_quest_should_be_400_uuid_bad_format() { #[actix_web::test] async fn update_quest_should_be_400_quest_validation_error() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let quest_definition = CreateQuestRequest { name: "QUEST-1".to_string(), @@ -261,7 +261,7 @@ async fn update_quest_should_be_400_quest_validation_error() { #[actix_web::test] async fn update_quest_should_be_401() { - let config = get_configuration().await; + let config = get_configuration(None).await; let app = init_service(build_app(&config).await).await; let quest_update = CreateQuestRequest { @@ -330,7 +330,7 @@ async fn update_quest_should_be_401() { #[actix_web::test] async fn update_quest_should_be_403() { - let config = get_configuration().await; + let config = get_configuration(None).await; let db = create_quests_db_component(&config.database_url, true) .await .unwrap(); diff --git a/crates/system/tests/event_processing.rs b/crates/system/tests/event_processing.rs index d6be364a..07c5bb6b 100644 --- a/crates/system/tests/event_processing.rs +++ b/crates/system/tests/event_processing.rs @@ -1,3 +1,4 @@ +mod common; use crate::common::database::create_test_db; use quests_db::{core::definitions::*, create_quests_db_component}; use quests_message_broker::messages_queue::MessagesQueue; @@ -13,8 +14,6 @@ use wiremock::{ Mock, MockServer, ResponseTemplate, }; -mod common; - #[tokio::test] async fn can_process_events() { let _ = env_logger::try_init(); @@ -98,6 +97,7 @@ async fn can_process_events() { let quest_instance_id = result.unwrap(); let mut config = Config::new().expect("Can parse config"); + config.redis_url = "127.0.0.1:6379/2".to_string(); config.database_url = db_url; let event_processor = EventProcessor::from_config(&config) .await @@ -221,7 +221,7 @@ async fn should_call_rewards_hook_when_user_completes_a_quest() { let quest_instance_id = db.start_quest(&quest_id, user_address).await.unwrap(); let mut config = Config::new().expect("Can parse config"); - config.redis_url = "127.0.0.1:6379/2".to_string(); + config.redis_url = "127.0.0.1:6379/3".to_string(); config.database_url = db_url; let event_processor = EventProcessor::from_config(&config) .await