From 0f7ed368de127db56f15f0bc0645e83c95e991da Mon Sep 17 00:00:00 2001 From: David Baque Date: Tue, 2 Apr 2024 20:01:55 +0200 Subject: [PATCH 01/47] creation service bug nullptr fixed --- include/models/service_model.h | 7 +++---- src/controllers/service_controller.cpp | 6 +++--- src/models/service_model.cpp | 9 +++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/include/models/service_model.h b/include/models/service_model.h index 2741fc3..15f8dc9 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include #include @@ -10,7 +11,6 @@ class ServiceModel { private: std::string _id; - std::string _community_id; std::string _creator_id; std::optional _buyer_id; std::string _title; @@ -23,10 +23,9 @@ class ServiceModel { std::string _updated_at; public: - ServiceModel(std::string id, std::string community_id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); + ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); std::string get_id() const; - std::string get_community_id() const; std::string get_creator_id() const; std::optional get_buyer_id() const; std::string get_title() const; @@ -38,7 +37,7 @@ class ServiceModel { std::string get_created_at() const; std::string get_updated_at() const; - static std::unique_ptr create_service(pqxx::connection& db, const std::string& community_id, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); + static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); static std::vector> get_open_services_by_community_id(pqxx::connection& db, const std::string& community_id); }; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 08ddc59..1ad0786 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -16,7 +16,9 @@ void ServiceController::create_service(pqxx::connection &db, const crow::request std::unique_ptr user = UserModel::get_user_by_id(db, creator_id); - std::unique_ptr service = ServiceModel::create_service(db, user.get()->get_community_id(), creator_id, title, description, price, type, nullptr); + std::cout << "id creator -> " << creator_id << std::endl; + + std::unique_ptr service = ServiceModel::create_service(db, creator_id, title, description, price, type, std::nullopt); if (!service) { handle_error(res, "internal server error", 500); @@ -25,7 +27,6 @@ void ServiceController::create_service(pqxx::connection &db, const crow::request crow::json::wvalue data( {{"id", service.get()->get_id()}, - {"community_id", service.get()->get_community_id()}, {"creator_id", service.get()->get_creator_id()}, {"title", service.get()->get_title()}, {"description", service.get()->get_description()}, @@ -61,7 +62,6 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & crow::json::wvalue service; service["id"] = all_services[i].get()->get_id(); - service["community_id"] = all_services[i].get()->get_community_id(); service["creator_id"] = all_services[i].get()->get_creator_id(); if (all_services[i].get()->get_buyer_id().has_value()) service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index d52981c..eadab41 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -1,9 +1,8 @@ #include -ServiceModel::ServiceModel(std::string id, std::string community_id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at) : _id(id), _community_id(community_id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at) {} +ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at) {} std::string ServiceModel::get_id() const { return _id; } -std::string ServiceModel::get_community_id() const { return _community_id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } std::optional ServiceModel::get_buyer_id() const { return _buyer_id; } std::string ServiceModel::get_title() const { return _title; } @@ -15,10 +14,10 @@ std::optional ServiceModel::get_image_url() const { return _image_u std::string ServiceModel::get_created_at() const { return _created_at; } std::string ServiceModel::get_updated_at() const { return _updated_at; } -std::unique_ptr ServiceModel::create_service(pqxx::connection& db, const std::string& community_id, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow) { +std::unique_ptr ServiceModel::create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow) { pqxx::work txn(db); - pqxx::result result = txn.exec_params("INSERT INTO services (community_id, creator_id, title, description, price, type) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, community_id, creator_id, buyer_id, title, description, price, status, type, image_url, created_at, updated_at", community_id, creator_id, title, description, price, type); + pqxx::result result = txn.exec_params("INSERT INTO services (creator_id, title, description, price, type) VALUES ($1, $2, $3, $4, $5) RETURNING id, creator_id, buyer_id, title, description, price, status, type, image_url, created_at, updated_at", creator_id, title, description, price, type); txn.commit(); @@ -42,7 +41,6 @@ std::unique_ptr ServiceModel::create_service(pqxx::connection& db, return std::make_unique( result[0]["id"].as(), - result[0]["community_id"].as(), result[0]["creator_id"].as(), buyer_id_field, result[0]["title"].as(), @@ -78,7 +76,6 @@ std::vector> ServiceModel::get_open_services_by_co all_services.push_back(std::make_unique( row["id"].as(), - row["community_id"].as(), row["creator_id"].as(), buyer_id_field, row["title"].as(), From 0a9d2d76188756476c3ed8e19e415ef18ac04936 Mon Sep 17 00:00:00 2001 From: David Baque Date: Tue, 2 Apr 2024 21:57:06 +0200 Subject: [PATCH 02/47] task #102: refactor get services --- include/controllers/service_controller.h | 1 + include/models/service_model.h | 11 +++- include/models/user_model.h | 6 +- src/controllers/service_controller.cpp | 38 ++++++++++- src/models/service_model.cpp | 84 ++++++++++++++++++++++-- src/models/user_model.cpp | 3 + 6 files changed, 133 insertions(+), 10 deletions(-) diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index 7f83356..ebf0aa5 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -13,6 +13,7 @@ #include #include #include +#include // ** --------------------------------------------- class ServiceController { diff --git a/include/models/service_model.h b/include/models/service_model.h index 15f8dc9..3b3b1e5 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -21,9 +22,13 @@ class ServiceModel { std::optional _image_url; std::string _created_at; std::string _updated_at; + UserModel _creator; + UserModel _buyer; public: - ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); + ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); + + ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer); std::string get_id() const; std::string get_creator_id() const; @@ -36,8 +41,10 @@ class ServiceModel { std::optional get_image_url() const; std::string get_created_at() const; std::string get_updated_at() const; + UserModel get_creator() const; + UserModel get_buyer() const; static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); - static std::vector> get_open_services_by_community_id(pqxx::connection& db, const std::string& community_id); + static std::vector> get_services(pqxx::connection& db, const std::string& community_id, const std::string& status = ""); }; diff --git a/include/models/user_model.h b/include/models/user_model.h index 87fc6da..c6c6a11 100755 --- a/include/models/user_model.h +++ b/include/models/user_model.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include #include @@ -12,7 +13,7 @@ class UserModel { private: std::string _id; std::string _community_id; - std::string _username; + std::string _username; std::string _email; std::string _type; int _balance; @@ -20,6 +21,9 @@ class UserModel { std::string _updated_at; public: + UserModel(); + UserModel(std::string id, std::string username); + UserModel(std::string id, std::string community_id, std::string username, std::string email, std::string type, int balance, std::string created_at, std::string updated_at); std::string get_id() const; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 1ad0786..487bc81 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -55,7 +55,19 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & crow::json::rvalue body = crow::json::load(req.body); std::unique_ptr user = UserModel::get_user_by_id(db, body["id"].s()); - std::vector> all_services = ServiceModel::get_open_services_by_community_id(db, user.get()->get_community_id()); + std::vector> all_services; + + auto status = req.url_params.get("status"); + + if (!status) { + all_services = ServiceModel::get_services(db, user.get()->get_community_id()); + + } else if (status && (std::string(status) == ServiceStatus::CLOSED || std::string(status) == ServiceStatus::OPEN)) { + all_services = ServiceModel::get_services(db, user.get()->get_community_id(), status); + } else { + handle_error(res, "status not valid value", 400); + return; + } crow::json::wvalue::list services; for (unsigned int i = 0; i < all_services.size(); ++i) { @@ -63,8 +75,19 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["id"] = all_services[i].get()->get_id(); service["creator_id"] = all_services[i].get()->get_creator_id(); - if (all_services[i].get()->get_buyer_id().has_value()) + if (all_services[i].get()->get_buyer_id().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = all_services[i].get()->get_buyer().get_id(); + buyer["username"] = all_services[i].get()->get_buyer().get_username(); + buyer["type"] = all_services[i].get()->get_buyer().get_type(); + buyer["email"] = all_services[i].get()->get_buyer().get_email(); + buyer["balance"] = all_services[i].get()->get_buyer().get_balance(); + buyer["created_at"] = all_services[i].get()->get_buyer().get_created_at(); + buyer["updated_at"] = all_services[i].get()->get_buyer().get_updated_at(); + + service["buyer"] = crow::json::wvalue(buyer); service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); + } service["title"] = all_services[i].get()->get_title(); service["description"] = all_services[i].get()->get_description(); service["price"] = all_services[i].get()->get_price(); @@ -75,6 +98,17 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["created_at"] = all_services[i].get()->get_created_at(); service["updated_at"] = all_services[i].get()->get_updated_at(); + crow::json::wvalue creator; + creator["id"] = all_services[i].get()->get_creator().get_id(); + creator["username"] = all_services[i].get()->get_creator().get_username(); + creator["type"] = all_services[i].get()->get_creator().get_type(); + creator["email"] = all_services[i].get()->get_creator().get_email(); + creator["balance"] = all_services[i].get()->get_creator().get_balance(); + creator["created_at"] = all_services[i].get()->get_creator().get_created_at(); + creator["updated_at"] = all_services[i].get()->get_creator().get_updated_at(); + + service["creator"] = crow::json::wvalue(creator); + services.push_back(service); } diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index eadab41..df7b155 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -2,6 +2,8 @@ ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at) {} +ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at), _creator(creator), _buyer(buyer) {} + std::string ServiceModel::get_id() const { return _id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } std::optional ServiceModel::get_buyer_id() const { return _buyer_id; } @@ -13,6 +15,8 @@ std::string ServiceModel::get_type() const { return _type; } std::optional ServiceModel::get_image_url() const { return _image_url; } std::string ServiceModel::get_created_at() const { return _created_at; } std::string ServiceModel::get_updated_at() const { return _updated_at; } +UserModel ServiceModel::get_creator() const { return _creator; }; +UserModel ServiceModel::get_buyer() const { return _buyer; }; std::unique_ptr ServiceModel::create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow) { pqxx::work txn(db); @@ -52,13 +56,55 @@ std::unique_ptr ServiceModel::create_service(pqxx::connection& db, result[0]["created_at"].as(), result[0]["updated_at"].as()); } - -std::vector> ServiceModel::get_open_services_by_community_id(pqxx::connection& db, const std::string& community_id) { +std::vector> ServiceModel::get_services(pqxx::connection& db, const std::string& community_id, const std::string& status) { std::vector> all_services; pqxx::work txn(db); - pqxx::result result = txn.exec_params("SELECT id, community_id, creator_id, buyer_id, title, description, price, status, type, image_url, created_at, updated_at FROM services WHERE community_id = $1", community_id); + std::string query = + "SELECT s.id AS service_id, " + "s.creator_id, " + "s.buyer_id, " + "s.title, " + "s.description, " + "s.price, " + "s.status, " + "s.type, " + "s.image_url, " + "s.created_at, " + "s.updated_at, " + "uc.id AS creator_id, " + "uc.community_id AS creator_community_id, " + "uc.username AS creator_username, " + "uc.email AS creator_email, " + "uc.type AS creator_type, " + "uc.balance AS creator_balance, " + "uc.created_at AS creator_created_at, " + "uc.updated_at AS creator_updated_at, " + "ub.id AS buyer_id, " + "ub.community_id AS buyer_community_id, " + "ub.username AS buyer_username, " + "ub.email AS buyer_email, " + "ub.type AS buyer_type, " + "ub.balance AS buyer_balance, " + "ub.created_at AS buyer_created_at, " + "ub.updated_at AS buyer_updated_at " + "FROM services AS s " + "JOIN users AS uc ON s.creator_id = uc.id " + "LEFT JOIN users AS ub ON s.buyer_id = ub.id " + "WHERE uc.community_id = $1"; + + // Agregar filtro de status si se proporciona + if (!status.empty()) { + query += " AND s.status = $2"; + } + + pqxx::result result; + if (!status.empty()) { + result = txn.exec_params(query, community_id, status); + } else { + result = txn.exec_params(query, community_id); + } txn.commit(); @@ -74,8 +120,34 @@ std::vector> ServiceModel::get_open_services_by_co else image_url_field = std::nullopt; + // Crear instancia de UserModel para el creador + UserModel creator( + row["creator_id"].as(), + row["creator_community_id"].as(), + row["creator_username"].as(), + row["creator_email"].as(), + row["creator_type"].as(), + row["creator_balance"].as(), + row["creator_created_at"].as(), + row["creator_updated_at"].as()); + + // Crear instancia de UserModel para el comprador, si existe + UserModel buyer; + if (buyer_id_field) { + buyer = UserModel( + row["buyer_id"].as(), + row["buyer_community_id"].as(), + row["buyer_username"].as(), + row["buyer_email"].as(), + row["buyer_type"].as(), + row["buyer_balance"].as(), + row["buyer_created_at"].as(), + row["buyer_updated_at"].as()); + } + + // Crear instancia de ServiceModel con UserModel como argumento adicional all_services.push_back(std::make_unique( - row["id"].as(), + row["service_id"].as(), row["creator_id"].as(), buyer_id_field, row["title"].as(), @@ -85,7 +157,9 @@ std::vector> ServiceModel::get_open_services_by_co row["type"].as(), image_url_field, row["created_at"].as(), - row["updated_at"].as())); + row["updated_at"].as(), + creator, + buyer)); } return all_services; diff --git a/src/models/user_model.cpp b/src/models/user_model.cpp index 3338405..b3efdbe 100755 --- a/src/models/user_model.cpp +++ b/src/models/user_model.cpp @@ -1,5 +1,8 @@ #include +UserModel::UserModel() : _id("") {} +UserModel::UserModel(std::string id, std::string username) : _id(id), _username(username) {} + UserModel::UserModel(std::string id, std::string community_id, std::string username, std::string email, std::string type, int balance, std::string created_at, std::string updated_at) : _id(id), _community_id(community_id), _username(username), _email(email), _type(type), _balance(balance), _created_at(created_at), _updated_at(updated_at) {} std::string UserModel::get_id() const { return _id; } From 9128a29ae2e606a78413ac1b650483693b0211f4 Mon Sep 17 00:00:00 2001 From: David Baque Date: Tue, 2 Apr 2024 22:22:30 +0200 Subject: [PATCH 03/47] refactor auth test --- test/tests/{ => auth}/auth_routes_test.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename test/tests/{ => auth}/auth_routes_test.cpp (98%) diff --git a/test/tests/auth_routes_test.cpp b/test/tests/auth/auth_routes_test.cpp similarity index 98% rename from test/tests/auth_routes_test.cpp rename to test/tests/auth/auth_routes_test.cpp index 8c32f87..61c43eb 100755 --- a/test/tests/auth_routes_test.cpp +++ b/test/tests/auth/auth_routes_test.cpp @@ -1,12 +1,14 @@ -#include #include #include + #include // Para std::getenv #include #include #include #include +#include "../common.h" + // Declaración de la función limpiarTablaUsers void limpiarTablaUsers() { try { @@ -349,7 +351,7 @@ TEST_F(RegisterGeneralErrors, UserAlredyExist) { ASSERT_TRUE(json.contains("error")); std::string error_message_email = json["error"]; - EXPECT_EQ(error_message_email, "user already exists"); + EXPECT_EQ(error_message_email, "email already in use"); EXPECT_EQ(response_email.status_code, 400); auto response_username = cpr::Post(cpr::Url{url}, cpr::Body{user_exist_username.dump()}, cpr::Header{{"Content-Type", "application/json"}}); @@ -358,7 +360,7 @@ TEST_F(RegisterGeneralErrors, UserAlredyExist) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; - EXPECT_EQ(error_message_username, "user already exists"); + EXPECT_EQ(error_message_username, "email already in use"); EXPECT_EQ(response_username.status_code, 400); } @@ -408,5 +410,5 @@ TEST_F(RegisterGeneralErrors, Community_Not_Exists) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; - EXPECT_EQ(error_message_username, "not community exists"); + EXPECT_EQ(error_message_username, "community does not exist"); } From 55306580166aca72f10ae112c3a4bde42fe644a3 Mon Sep 17 00:00:00 2001 From: David Baque Date: Tue, 2 Apr 2024 22:28:41 +0200 Subject: [PATCH 04/47] task #108: refactor testing architecture --- test/tests/auth/{auth_routes_test.cpp => register_test.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/tests/auth/{auth_routes_test.cpp => register_test.cpp} (100%) diff --git a/test/tests/auth/auth_routes_test.cpp b/test/tests/auth/register_test.cpp similarity index 100% rename from test/tests/auth/auth_routes_test.cpp rename to test/tests/auth/register_test.cpp From 5769c869361121b88e2a299f2322ba3bf0c1de21 Mon Sep 17 00:00:00 2001 From: David Baque Date: Wed, 3 Apr 2024 22:43:41 +0200 Subject: [PATCH 05/47] base --- include/middlewares/verify_jwt.h | 5 +++++ test/tests/services/create_service_test.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 test/tests/services/create_service_test.cpp diff --git a/include/middlewares/verify_jwt.h b/include/middlewares/verify_jwt.h index 29513a6..8eea6bb 100755 --- a/include/middlewares/verify_jwt.h +++ b/include/middlewares/verify_jwt.h @@ -11,6 +11,11 @@ struct VerifyJWT : crow::ILocalMiddleware { void before_handle(crow::request& req, crow::response& res, context& ctx) { std::string token = get_token_cookie(req); + if (token == "") { + handle_error(res, "not token provided", 404); + return; + } + if (!validate_token(token)) { handle_error(res, "invalid token", 401); return; diff --git a/test/tests/services/create_service_test.cpp b/test/tests/services/create_service_test.cpp new file mode 100644 index 0000000..e28d339 --- /dev/null +++ b/test/tests/services/create_service_test.cpp @@ -0,0 +1,10 @@ +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "../common.h" From 74ea7fcead99b11448bcf1e73e3631714cf51cff Mon Sep 17 00:00:00 2001 From: David Baque Date: Thu, 4 Apr 2024 20:42:35 +0200 Subject: [PATCH 06/47] add test documentation --- src/controllers/auth_controller.cpp | 2 +- test/TEST_README.md | 70 +++++++++ test/tests/auth/login_test.cpp | 0 test/tests/auth/register_test.cpp | 9 +- test/tests/common.cpp | 146 ++++++++++++++++++ test/tests/common.h | 37 ++++- test/tests/main.cpp | 1 + test/tests/service_routes_test.cpp | 28 ++++ test/tests/services/create_service.md | 31 ++++ test/tests/services/create_service_test.cpp | 10 -- test/tests/services/delete_service_test.cpp | 0 test/tests/services/get_all_services_test.cpp | 0 test/tests/services/get_service_test.cpp | 30 ++++ test/tests/services/get_services.md | 69 +++++++++ test/tests/services/update_service_test.cpp | 0 test/tests/test_one.cpp | 4 +- 16 files changed, 414 insertions(+), 23 deletions(-) create mode 100644 test/TEST_README.md create mode 100644 test/tests/auth/login_test.cpp mode change 100755 => 100644 test/tests/auth/register_test.cpp create mode 100644 test/tests/common.cpp mode change 100755 => 100644 test/tests/common.h mode change 100755 => 100644 test/tests/main.cpp create mode 100644 test/tests/service_routes_test.cpp create mode 100644 test/tests/services/create_service.md create mode 100644 test/tests/services/delete_service_test.cpp create mode 100644 test/tests/services/get_all_services_test.cpp create mode 100644 test/tests/services/get_service_test.cpp create mode 100644 test/tests/services/get_services.md create mode 100644 test/tests/services/update_service_test.cpp mode change 100755 => 100644 test/tests/test_one.cpp diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index d4c3d39..f9559e6 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -40,7 +40,7 @@ void AuthController::register_user(pqxx::connection &db, const crow::request &re } else if (type == Roles::NEIGHBOR) { std::unique_ptr community = CommunityModel::get_community_by_code(db, body["community_code"].s()); if (!community) { - handle_error(res, "community does not exist", 404); + handle_error(res, "community does not exists", 404); return; } community_id = community.get()->get_id(); diff --git a/test/TEST_README.md b/test/TEST_README.md new file mode 100644 index 0000000..cf17976 --- /dev/null +++ b/test/TEST_README.md @@ -0,0 +1,70 @@ +# Integration Testing for C++ Server + +## Project Description + +This project focuses on performing integration tests for all endpoints of a server implemented in C++. Integration tests are a type of test that verify the interaction between various components of a system to ensure their proper functioning together. + +## Tools Used + +The project utilizes the following tools for conducting integration tests: + +- **Google Test:** Google Test is a unit testing framework for C++. In this project, it's used to write and execute automated tests that verify the behavior of the server endpoints. + +- **CPR:** CPR is a C++ library that facilitates HTTP requests. It's used in this project to send requests to the server endpoints during integration testing. + +- **nlohmann/json:** nlohmann/json is a C++ library for JSON data handling. In this project, it's used to manipulate input and output data of the endpoints during integration testing. + +## Project Objective + +The primary objective of this project is to ensure that all server endpoints function correctly and integrate properly with other components of the system. This is achieved through the writing and execution of automated tests that cover different scenarios and use cases. + +## Project Structure + +The project is organized into different files and folders, including: + +- **`tests` Directory:** Contains the source code files for integration tests written with Google Test. + +- **`CMakeLists.txt` File:** This file is used to configure and generate the project's build system using CMake. + +- **Other Source Code Files:** In addition to test files, the project may include source code files that implement the server endpoints and other components necessary for integration testing. + +## Running the Tests + +To execute the integration tests, there are two different ways to run the tests: + +### Method 1: Automated Execution with Docker + +1. Make sure you are in the DevOps repository. +2. Bring up all necessary containers by running the `test.yml` file. This file will start all containers required to run the integration tests. + + ```bash + docker compose -f test.yml up --build + ``` + +3. Once Docker has finished bringing everything up, all existing tests will be automatically executed. + +### Method 2: Manual Execution within the Container + +1. In the `devops` repository, execute the following command to acccess the test container: + ```bash + docker exec -it test bash + ``` +2. Within the container, you can run the tests you're interested in. For example, if your tests have the following structure TEST(suite_name, test_name): + +3. Within the container, you can run the tests you're interested in. For example, if your tests have the following structure `TEST(suite_name, test_name)`: + - change to the test directory: + ```bash + cd test + ``` + - run a specific test using the Google Test filter: + ```bash + ./test --gtest_filter="suite_name.test_name" + ``` + - to run all test starting with a specific name, use the asterisk as a wildcard: + ```bash + ./test --gtest_filter="suite_name*" + ``` + +These methods allow you to execute the integration tests automatically or manually, depending on your needs and preferences. The second method is useful when you only need to run specific tests within the project due to time constraints or other considerations. + + diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/auth/register_test.cpp b/test/tests/auth/register_test.cpp old mode 100755 new mode 100644 index 61c43eb..b37e144 --- a/test/tests/auth/register_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -38,8 +38,7 @@ void limpiarTablaUsers() { class RegisterValidations : public ::testing::Test { protected: void TearDown() override { - // Llamar a la función limpiarTablaUsers después de que se complete el test - limpiarTablaUsers(); + clean_user_table(); } }; @@ -360,7 +359,7 @@ TEST_F(RegisterGeneralErrors, UserAlredyExist) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; - EXPECT_EQ(error_message_username, "email already in use"); + EXPECT_EQ(error_message_username, "email already in use"); EXPECT_EQ(response_username.status_code, 400); } @@ -410,5 +409,5 @@ TEST_F(RegisterGeneralErrors, Community_Not_Exists) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; - EXPECT_EQ(error_message_username, "community does not exist"); -} + EXPECT_EQ(error_message_username, "community does not exists"); +} \ No newline at end of file diff --git a/test/tests/common.cpp b/test/tests/common.cpp new file mode 100644 index 0000000..81b3464 --- /dev/null +++ b/test/tests/common.cpp @@ -0,0 +1,146 @@ +#include "common.h" + +#include // Para std::getenv +#include +#include +#include + +const int HTTP_PORT = std::stoi(std::getenv("HTTP_PORT")); +const std::string DB_NAME = std::string(std::getenv("DB_NAME")); +const std::string DB_USER = std::string(std::getenv("DB_USER")); +const std::string DB_PASSWORD = std::string(std::getenv("DB_PASSWORD")); +const std::string DB_HOST = std::string(std::getenv("DB_HOST")); +const int DB_PORT = std::stoi(std::getenv("DB_PORT")); +const std::string token_get_all_services = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpZCI6IjZjZWM1NzA0LTY0MjMtNDUyZC1iMDNjLTdmMDFiY2NhMWVjYyIsImlzcyI6ImF1dGgwIiwidHlwZSI6ImFkbWluIn0.PNlBpdYsY5Md-wxugYW6J2Sd3pD3HlrkKNxWrW2fs7A"; +const std::string connection_string = std::format("dbname={} user={} password={} host={} port={}", DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT); + +void clean_user_table() { + try { + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + pqxx::work txn(conn); + + txn.exec("DELETE FROM users"); + txn.exec("DELETE FROM communities"); + + txn.commit(); + + } else { + std::cout << "Error al conectar a la base de datos." << std::endl; + } + } catch (const std::exception& e) { + std::cout << "Error de excepción: " << e.what() << std::endl; + } +} + +void clean_community_table() { + try { + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + pqxx::work txn(conn); + + txn.exec("DELETE FROM communities"); + + txn.commit(); + + } else { + std::cout << "Error al conectar a la base de datos." << std::endl; + } + } catch (const std::exception& e) { + std::cout << "Error de excepción: " << e.what() << std::endl; + } +} + +std::string create_user_test() { + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + // Sample user data (replace with your actual user data) + std::string username = "test_user"; + std::string email = "test@example.com"; + std::string password = "password123"; + std::string image_url = "https://example.com/image.jpg"; + std::string type = "neighbor"; // or "admin" depending on the user's type + + // Insert user into the database + pqxx::result result = txn.exec_params("INSERT INTO users (username, email, password, image_url, type) VALUES ($1, $2, $3, $4, $5) RETURNING id", + username, email, password, image_url, type); + + txn.commit(); + + // Return the ID of the newly created user + return result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + + return ""; // Return empty string if user creation fails +} + +void create_services() { + std::string user_id = create_user_test(); + + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + // Create 5 example services for testing + for (int i = 1; i <= 5; ++i) { + // Sample service data (replace with your actual service data) + std::string title = "Service " + std::to_string(i); + std::string description = "Description of service " + std::to_string(i); + int price = 100 * i; // Sample price + std::string type = "REQUESTED"; // or "OFFERED" depending on the service type + + // Insert service into the database + txn.exec_params("INSERT INTO services (creator_id, title, description, price, type) VALUES ($1, $2, $3, $4, $5)", + user_id, title, description, price, type); + } + + txn.commit(); + + std::cout << "Example services created successfully." << std::endl; + } catch (const std::exception& e) { + std::cerr << "Error creating example services: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } +} + +std::string register_and_get_user_token() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + return token_value; + } else { + // La cookie "token" no se encontró en el encabezado + return ""; // Devolver un valor por defecto o manejar el error según sea necesario + } +} diff --git a/test/tests/common.h b/test/tests/common.h old mode 100755 new mode 100644 index 1a00ca0..235f5d4 --- a/test/tests/common.h +++ b/test/tests/common.h @@ -1,6 +1,31 @@ -int HTTP_PORT = std::stoi(std::getenv("HTTP_PORT")); -std::string DB_NAME = std::string(std::getenv("DB_NAME")); -std::string DB_USER = std::string(std::getenv("DB_USER")); -std::string DB_PASSWORD = std::string(std::getenv("DB_PASSWORD")); -std::string DB_HOST = std::string(std::getenv("DB_HOST")); -std::string DB_PORT = std::string(std::getenv("DB_PORT")); +#ifndef COMMON_HPP +#define COMMON_HPP + +#include + +#include +#include + +extern const std::string DB_NAME; +extern const std::string DB_USER; +extern const std::string DB_PASSWORD; +extern const std::string DB_HOST; +extern const int DB_PORT; +extern const int HTTP_PORT; +extern const std::string token_get_all_services; +extern const std::string connection_string; + +void clean_user_table(); +void clean_community_table(); + +std::string create_user_test(); + +// ? Creates a new user and fetches their authentication JWT token from the cookie. +// ? This token is necessary for testing other routes requiring authentication and permissions. +std::string register_and_get_user_token(); + +//** mock services to test: +//* - GET /api/services?status=OPEN +void create_services(); + +#endif // COMMON_HPP diff --git a/test/tests/main.cpp b/test/tests/main.cpp old mode 100755 new mode 100644 index a115f6f..926358d --- a/test/tests/main.cpp +++ b/test/tests/main.cpp @@ -1,4 +1,5 @@ #include + #include int main(int argc, char** argv) { diff --git a/test/tests/service_routes_test.cpp b/test/tests/service_routes_test.cpp new file mode 100644 index 0000000..4ef5287 --- /dev/null +++ b/test/tests/service_routes_test.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "common.h" + +class GetAllServices : public ::testing::Test { + protected: + void SetUp() override { + // Code here will be called immediately after the constructor (right + // before each test). + } + /* void TearDown() override { + clean_user_table(); + } */ +}; + +// Define your test cases within this class or other test fixture classes + +TEST_F(GetAllServices, first) { + // create_services(); + EXPECT_EQ(2, 2); +} diff --git a/test/tests/services/create_service.md b/test/tests/services/create_service.md new file mode 100644 index 0000000..d10026f --- /dev/null +++ b/test/tests/services/create_service.md @@ -0,0 +1,31 @@ +# ServiceController::create_service Endpoint Testing Documentation + +This document outlines the testing scenarios for the `create_service` endpoint in the `ServiceController` class. + +## Test Scenarios + +1. **Valid Request** + - **Description:** Test the endpoint with a valid request containing all required fields. + - **Inputs:** + - Valid JSON body containing required fields (`title`, `description`, `price`, `id`, `type`). + - **Expected Output:** + - HTTP status code: 201. + - JSON response containing the created service details, including ID, creator ID, title, description, price, status, type, created_at, and updated_at fields. + +2. **Missing Required Field** + - **Description:** Test the endpoint with a request missing one of the required fields. + - **Inputs:** + - JSON body missing one of the required fields (`title`, `description`, `price`, `id`, `type`). + - **Expected Output:** + - HTTP status code: 400. + - Error message indicating the missing required field. + +3. **Invalid Creator ID** + - **Description:** Test the endpoint with an invalid creator ID. + - **Inputs:** + - Valid JSON body containing all required fields. + - Invalid creator ID. + - **Expected Output:** + - HTTP status code: 500. + - Error message indicating an internal server error. + diff --git a/test/tests/services/create_service_test.cpp b/test/tests/services/create_service_test.cpp index e28d339..e69de29 100644 --- a/test/tests/services/create_service_test.cpp +++ b/test/tests/services/create_service_test.cpp @@ -1,10 +0,0 @@ -#include -#include - -#include // Para std::getenv -#include -#include -#include -#include - -#include "../common.h" diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp new file mode 100644 index 0000000..7a4e08e --- /dev/null +++ b/test/tests/services/get_service_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "../common.h" + +TEST(GET_SERVICES, first) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + + std::cout << " test -> " << response.text << std::endl; + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("services")); + + ASSERT_TRUE(json["services"].is_array()); + + clean_user_table(); + clean_community_table(); +} + diff --git a/test/tests/services/get_services.md b/test/tests/services/get_services.md new file mode 100644 index 0000000..8a0168c --- /dev/null +++ b/test/tests/services/get_services.md @@ -0,0 +1,69 @@ +# ServiceController::get_services Endpoint Testing Documentation + +This document outlines the testing scenarios for the `get_services` endpoint in the `ServiceController` class. + +## Test Scenarios + +1. **Valid Request** + - **Description:** Test the endpoint with a valid request from an authenticated user. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 200. + - JSON response containing an array of services belonging to the user's community. + +2. **Valid Request with Status Filter** + - **Description:** Test the endpoint with a valid request including a status filter. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - Status parameter in the query string (`open` or `closed`). + - **Expected Output:** + - HTTP status code: 200. + - JSON response containing an array of services belonging to the user's community with the specified status. + +3. **Invalid Status Parameter** + - **Description:** Test the endpoint with an invalid status parameter. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - Invalid status parameter in the query string. + - **Expected Output:** + - HTTP status code: 400. + - Error message indicating that the status parameter is not a valid value. + +4. **Unauthorized Access** + - **Description:** Test the endpoint without providing a JWT token. + - **Inputs:** + - No JWT token provided. + - User ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 401. + - Error message indicating unauthorized access. + +5. **Invalid User ID** + - **Description:** Test the endpoint with an invalid or missing user ID. + - **Inputs:** + - Valid JWT token via cookies. + - Invalid or missing user ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 400. + - Error message indicating an invalid or missing user ID. + +6. **Internal Server Error** + - **Description:** Test the endpoint when an internal server error occurs during processing. + - **Inputs:** + - Valid JWT token via cookies. + - Valid user ID in the request body. + - Community ID associated with the user. + - Trigger an internal server error within the endpoint. + - **Expected Output:** + - HTTP status code: 500. + - Error message indicating an internal server error. + diff --git a/test/tests/services/update_service_test.cpp b/test/tests/services/update_service_test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/test_one.cpp b/test/tests/test_one.cpp old mode 100755 new mode 100644 index 64b146b..1ed0154 --- a/test/tests/test_one.cpp +++ b/test/tests/test_one.cpp @@ -1,10 +1,12 @@ #include #include + #include // Para std::getenv #include #include #include + int sum(int a, int b) { return a + b; } @@ -94,4 +96,4 @@ std::string simulateLoginAndGetToken() { ASSERT_TRUE(user.contains("type")); } } - */ + */ \ No newline at end of file From 99f738d18d4746d0edc1200b12c4ed8a8ba47bac Mon Sep 17 00:00:00 2001 From: David Baque Date: Thu, 4 Apr 2024 20:44:20 +0200 Subject: [PATCH 07/47] add test link readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 781894a..5cc5d5d 100755 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # Backend + +[TEST](test/TEST_README.md) \ No newline at end of file From 2d998769a75491148df2e6eb4d80f3c7f6c0fe2c Mon Sep 17 00:00:00 2001 From: David Baque Date: Thu, 4 Apr 2024 21:07:19 +0200 Subject: [PATCH 08/47] add section testing organization --- test/TEST_README.md | 23 ++++++++++++++-- test/tests/auth/lgoin.md | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 test/tests/auth/lgoin.md diff --git a/test/TEST_README.md b/test/TEST_README.md index cf17976..850b7bb 100644 --- a/test/TEST_README.md +++ b/test/TEST_README.md @@ -1,5 +1,15 @@ # Integration Testing for C++ Server +## Table of Contents +1. [Project Description](#project-description) +2. [Tools Used](#tools-used) +3. [Project Objective](#project-objective) +4. [Project Structure](#project-structure) +5. [Running the Tests](#running-the-tests) + - [Method 1: Automated Execution with Docker](#method-1-automated-execution-with-docker) + - [Method 2: Manual Execution within the Container](#method-2-manual-execution-within-the-container) +6. [Tests Organization](#tests-organization) + ## Project Description This project focuses on performing integration tests for all endpoints of a server implemented in C++. Integration tests are a type of test that verify the interaction between various components of a system to ensure their proper functioning together. @@ -49,9 +59,7 @@ To execute the integration tests, there are two different ways to run the tests: ```bash docker exec -it test bash ``` -2. Within the container, you can run the tests you're interested in. For example, if your tests have the following structure TEST(suite_name, test_name): - -3. Within the container, you can run the tests you're interested in. For example, if your tests have the following structure `TEST(suite_name, test_name)`: +2. Within the container, you can run the tests you're interested in. For example, if your tests have the following structure `TEST(suite_name, test_name)`: - change to the test directory: ```bash cd test @@ -67,4 +75,13 @@ To execute the integration tests, there are two different ways to run the tests: These methods allow you to execute the integration tests automatically or manually, depending on your needs and preferences. The second method is useful when you only need to run specific tests within the project due to time constraints or other considerations. +## Tests Organization + +### Auth +- ["GET /api/auth/register"](tests/auth/lgoin.md) + +### Users + +### Services +### Notifications diff --git a/test/tests/auth/lgoin.md b/test/tests/auth/lgoin.md new file mode 100644 index 0000000..3e061e5 --- /dev/null +++ b/test/tests/auth/lgoin.md @@ -0,0 +1,59 @@ +# GET /api/auth/register Endpoint Testing Documentation + +This document outlines the testing scenarios for the `register_user` endpoint in the `AuthController` class. + +## Test Scenarios + +1. **Valid Registration** + - **Description:** Test the endpoint with valid user registration data. + - **Inputs:** + - Username, email, password, and user type in the request body. + - **Expected Output:** + - HTTP status code: 201 (Created). + - JSON response containing the ID of the newly registered user. + ```json + { + "id": "user_id_registered" + } + ``` + - Set-Cookie header with JWT token for authentication. + +2. **Duplicate Username** + - **Description:** Test the endpoint with a username that already exists in the database. + - **Inputs:** + - Existing username in the request body. + - **Expected Output:** + - HTTP status code: 400 (Bad Request). + - Error message indicating that the username is already in use. + +3. **Duplicate Email** + - **Description:** Test the endpoint with an email address that already exists in the database. + - **Inputs:** + - Existing email address in the request body. + - **Expected Output:** + - HTTP status code: 400 (Bad Request). + - Error message indicating that the email address is already in use. + +4. **Register Admin User with Invalid Community Name** + - **Description:** Test the endpoint to register an admin user with an invalid community name. + - **Inputs:** + - Admin user data with an invalid community name. + - **Expected Output:** + - HTTP status code: 500 (Internal Server Error). + - Error message indicating an internal server error. + +5. **Register Neighbor User with Nonexistent Community Code** + - **Description:** Test the endpoint to register a neighbor user with a nonexistent community code. + - **Inputs:** + - Neighbor user data with a nonexistent community code. + - **Expected Output:** + - HTTP status code: 404 (Not Found). + - Error message indicating that the community does not exist. + +6. **Internal Server Error** + - **Description:** Test the endpoint when an internal server error occurs during processing. + - **Inputs:** + - Trigger an internal server error within the endpoint. + - **Expected Output:** + - HTTP status code: 500 (Internal Server Error). + - Error message indicating an internal server error. From b2743efaf0f1c1caa014fc932ec8967092d9504c Mon Sep 17 00:00:00 2001 From: David Baque Date: Thu, 4 Apr 2024 21:58:41 +0200 Subject: [PATCH 09/47] add test login --- src/controllers/auth_controller.cpp | 11 ++- test/tests/auth/lgoin.md | 8 -- test/tests/auth/login_test.cpp | 116 +++++++++++++++++++++++ test/tests/auth/register_test.cpp | 4 +- test/tests/services/get_service_test.cpp | 3 - 5 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index f9559e6..aa8403f 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -1,6 +1,7 @@ #include #include #include + #include #include // Include the ctime header for time functions #include @@ -133,17 +134,17 @@ void AuthController::login_user(pqxx::connection &db, const crow::request &req, res.set_header("Set-Cookie", cookieStream.str()); - crow::json::wvalue data; - data["user"] = { - {"id", user.get()->get_id()}, - {"type", user.get()->get_type()}}; + crow::json::wvalue data( + { + {"id", user.get()->get_id()}, + }); res.code = 200; res.write(data.dump()); res.end(); } else { - handle_error(res, "password invalid", 400); + handle_error(res, "invalid password", 400); return; } diff --git a/test/tests/auth/lgoin.md b/test/tests/auth/lgoin.md index 3e061e5..e84c95e 100644 --- a/test/tests/auth/lgoin.md +++ b/test/tests/auth/lgoin.md @@ -49,11 +49,3 @@ This document outlines the testing scenarios for the `register_user` endpoint in - **Expected Output:** - HTTP status code: 404 (Not Found). - Error message indicating that the community does not exist. - -6. **Internal Server Error** - - **Description:** Test the endpoint when an internal server error occurs during processing. - - **Inputs:** - - Trigger an internal server error within the endpoint. - - **Expected Output:** - - HTTP status code: 500 (Internal Server Error). - - Error message indicating an internal server error. diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp index e69de29..6ed699e 100644 --- a/test/tests/auth/login_test.cpp +++ b/test/tests/auth/login_test.cpp @@ -0,0 +1,116 @@ +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "../common.h" + +TEST(LOGIN_ERRORS, email_not_exists) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + + nlohmann::json post_data = { + {"email", "not_email_exist@gmail.com"}, + {"password", "F!sh1ngR0ck5"}, + }; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("error")); + + EXPECT_EQ(json["error"], "no user found with this email"); + + EXPECT_EQ(response.status_code, 404); +} + +TEST(LOGIN_ERRORS, incorrect_password) { + // * Before_test + std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + std::string email = "example@gmail.com"; + std::string password = "P@ssw0rd!"; + + nlohmann::json new_user = { + {"email", email}, + {"username", "tupapiloko"}, + {"password", password}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + // * Test + + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + + nlohmann::json post_data = { + {"email", email}, + {"password", "Incorrect_password1"}, + }; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("error")); + + EXPECT_EQ(json["error"], "invalid password"); + + EXPECT_EQ(response.status_code, 400); + + // * After_test + clean_user_table(); + clean_community_table(); +} + +TEST(LOGIN, correct_login) { + // * Before_test + std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + std::string email = "example@gmail.com"; + std::string password = "P@ssw0rd!"; + + nlohmann::json new_user = { + {"email", email}, + {"username", "tupapiloko"}, + {"password", password}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + // * Test + + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + + nlohmann::json post_data = { + {"email", email}, + {"password", password}, + }; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 200); + ASSERT_TRUE(json.contains("id")); + ASSERT_TRUE(json["id"].is_string()); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + // Buscar la cookie "token" dentro del encabezado "Set-Cookie" + size_t token_pos = set_cookie_header.find("token="); + bool token_found = token_pos != std::string::npos; + + // Verificar que se encontró la cookie "token" + EXPECT_TRUE(token_found); + + // * After_test + clean_user_table(); + clean_community_table(); +} \ No newline at end of file diff --git a/test/tests/auth/register_test.cpp b/test/tests/auth/register_test.cpp index b37e144..50a561f 100644 --- a/test/tests/auth/register_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -359,7 +359,7 @@ TEST_F(RegisterGeneralErrors, UserAlredyExist) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; - EXPECT_EQ(error_message_username, "email already in use"); + EXPECT_EQ(error_message_username, "email already in use"); EXPECT_EQ(response_username.status_code, 400); } @@ -389,6 +389,8 @@ TEST_F(RegisterGeneralErrors, CorrectSignup) { // Verificar que se encontró la cookie "token" EXPECT_TRUE(token_found); + clean_user_table(); + clean_community_table(); } TEST_F(RegisterGeneralErrors, Community_Not_Exists) { diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index 7a4e08e..8871b8e 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -14,8 +14,6 @@ TEST(GET_SERVICES, first) { auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); - std::cout << " test -> " << response.text << std::endl; - EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; auto json = nlohmann::json::parse(response.text); @@ -27,4 +25,3 @@ TEST(GET_SERVICES, first) { clean_user_table(); clean_community_table(); } - From 321df0c8e9e94da00e6ef748d4c5f669e8ce67fe Mon Sep 17 00:00:00 2001 From: David Baque Date: Fri, 5 Apr 2024 15:26:13 +0200 Subject: [PATCH 10/47] task #109: add login tests --- src/controllers/auth_controller.cpp | 10 -- test/tests/auth/login_test.cpp | 197 ++++++++++++++++++++-------- 2 files changed, 139 insertions(+), 68 deletions(-) diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index aa8403f..8a28351 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -87,16 +87,6 @@ void AuthController::register_user(pqxx::connection &db, const crow::request &re void AuthController::login_user(pqxx::connection &db, const crow::request &req, crow::response &res) { try { - /* crow::json::rvalue body = crow::json::load(req.body); - - std::string id = body["id"].s(); - - crow::json::wvalue data({{"id", id}}); - - res.code = 200; - res.write(data.dump()); - - res.end(); */ if (!is_correct_body_login(req, res)) return; crow::json::rvalue body = crow::json::load(req.body); diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp index 6ed699e..4d63bbd 100644 --- a/test/tests/auth/login_test.cpp +++ b/test/tests/auth/login_test.cpp @@ -9,48 +9,101 @@ #include "../common.h" -TEST(LOGIN_ERRORS, email_not_exists) { +class LoginTest : public testing::Test { + protected: + std::string email_; + std::string password_; + + void SetUp() override { + email_ = "example@gmail.com"; + password_ = "P@ssw0rd!"; + + std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", email_}, + {"username", "tupapiloko"}, + {"password", password_}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + } + + void TearDown() override { + clean_user_table(); + clean_community_table(); + } +}; + +class LoginErrorsTest : public testing::Test { + protected: + std::string email_; + std::string password_; + + void SetUp() override { + email_ = "example@gmail.com"; + password_ = "P@ssw0rd!"; + + std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", email_}, + {"username", "tupapiloko"}, + {"password", password_}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + } + + void TearDown() override { + clean_user_table(); + clean_community_table(); + } +}; + +TEST_F(LoginTest, correct_login) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; - nlohmann::json post_data = { - {"email", "not_email_exist@gmail.com"}, - {"password", "F!sh1ngR0ck5"}, + {"email", email_}, + {"password", password_}, }; auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - ASSERT_TRUE(json.contains("error")); + EXPECT_EQ(response.status_code, 200); + ASSERT_TRUE(json.contains("id")); + ASSERT_TRUE(json["id"].is_string()); - EXPECT_EQ(json["error"], "no user found with this email"); + std::string set_cookie_header = response.header["Set-Cookie"]; + size_t token_pos = set_cookie_header.find("token="); + bool token_found = token_pos != std::string::npos; - EXPECT_EQ(response.status_code, 404); + EXPECT_TRUE(token_found); } -TEST(LOGIN_ERRORS, incorrect_password) { - // * Before_test - std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - std::string email = "example@gmail.com"; - std::string password = "P@ssw0rd!"; - - nlohmann::json new_user = { - {"email", email}, - {"username", "tupapiloko"}, - {"password", password}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; +// Prueba de inicio de sesión con contraseña incorrecta +TEST_F(LoginErrorsTest, incorrect_password) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + nlohmann::json post_data = { + {"email", email_}, + {"password", "Incorrect_password1"}, + }; - auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); - // * Test + ASSERT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid password"); + EXPECT_EQ(response.status_code, 400); +} +TEST_F(LoginErrorsTest, email_not_exists) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; nlohmann::json post_data = { - {"email", email}, - {"password", "Incorrect_password1"}, + {"email", "not_email_exist@gmail.com"}, + {"password", "F!sh1ngR0ck5"}, }; auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); @@ -59,58 +112,86 @@ TEST(LOGIN_ERRORS, incorrect_password) { ASSERT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "invalid password"); + EXPECT_EQ(json["error"], "no user found with this email"); - EXPECT_EQ(response.status_code, 400); + EXPECT_EQ(response.status_code, 404); +} + +TEST(LoginValidationInputTest, IncorrectEmail) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; - // * After_test - clean_user_table(); - clean_community_table(); + // Lista de direcciones de correo electrónico incorrectas + std::vector incorrect_emails = { + "example%@gmail.com", + "example@domain.", + "example@domain123", + "example@domain,com", + "example@domain.com.", + "example@@domain.com", + "example@domain..com", + "example@@domain..com", + "example@domain_com", + "example@domain.com_com"}; + + for (const auto& email : incorrect_emails) { + nlohmann::json post_data = { + {"email", email}, + {"password", "F!sh1ngR0ck5"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for incorrect email: " << email; + } } -TEST(LOGIN, correct_login) { - // * Before_test - std::string url_register = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; +TEST(LoginValidationInputTest, Incorrect_Password) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; - std::string email = "example@gmail.com"; - std::string password = "P@ssw0rd!"; + // Lista de direcciones de correo electrónico incorrectas + std::vector incorrect_passwords = { + "password", "12345678", "qwerty", "letmein", "abc123"}; - nlohmann::json new_user = { - {"email", email}, - {"username", "tupapiloko"}, - {"password", password}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; + for (const auto& password : incorrect_passwords) { + nlohmann::json post_data = { + {"email", "example@gmail.com"}, + {"password", password}}; - auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - // * Test + auto json = nlohmann::json::parse(response.text); - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for incorrect password: " << password; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "incorrect password"); + } +} +TEST(LoginValidationInputTest, missing_email) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; nlohmann::json post_data = { - {"email", email}, - {"password", password}, + {"password", "Hola123."}, }; auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 200); - ASSERT_TRUE(json.contains("id")); - ASSERT_TRUE(json["id"].is_string()); + EXPECT_EQ(response.status_code, 404); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "missing email field"); +} - std::string set_cookie_header = response.header["Set-Cookie"]; +TEST(LoginValidationInputTest, missing_password) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + nlohmann::json post_data = { + {"email", "example_email@gmail.com"}, + }; - // Buscar la cookie "token" dentro del encabezado "Set-Cookie" - size_t token_pos = set_cookie_header.find("token="); - bool token_found = token_pos != std::string::npos; + auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - // Verificar que se encontró la cookie "token" - EXPECT_TRUE(token_found); + auto json = nlohmann::json::parse(response.text); - // * After_test - clean_user_table(); - clean_community_table(); + EXPECT_EQ(response.status_code, 404); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "missing password field"); } \ No newline at end of file From 0729d481b6dd0d4ede75c1e282354c84705b6161 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Mon, 22 Apr 2024 18:22:35 +0200 Subject: [PATCH 11/47] task #125: update self controller --- include/controllers/user_controller.h | 2 ++ src/controllers/user_controller.cpp | 33 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/controllers/user_controller.h b/include/controllers/user_controller.h index 0268f41..4483da1 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -5,6 +5,7 @@ #include #include #include + #include #include #include @@ -16,4 +17,5 @@ class UserController { static void get_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); static void delete_user_by_id(pqxx::connection& db, crow::response& res, const std::string& user_id); static void update_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); + static void update_self(pqxx::connection& db, const crow::request& req, crow::response& res); }; diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 75ddd43..3523d87 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -121,3 +121,36 @@ void UserController::update_user_by_id(pqxx::connection &db, const crow::request handle_error(res, "internal server error", 500); } } + +void UserController::update_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { + crow::json::rvalue update = crow::json::load(req.body); + user_id = update[id].s(); + std::string temp_name = "", temp_pass = "", temp_email = ""; + if (update.has("username")) { + temp_name = update["username"].s(); + if (!validate_username(temp_name, res)) return; + } + if (update.has("email")) { + temp_email = update["email"].s(); + if (!validate_email(temp_email, res)) return; + } + if (update.has("password")) { + temp_pass = update["password"].s(); + if (!validate_password(temp_pass, res)) return; + } + std::string hash = BCrypt::generateHash(temp_pass); + bool succes = UserModel::update_user_by_id(db, user_id, temp_name, temp_email, hash); + if (succes) { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "User updated successfully"; + res.write(response_message.dump()); + res.end(); + } else + handle_error(res, "internal server error", 500); + } catch (const std::exception &e) { + std::cerr << "Error updating user: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} \ No newline at end of file From 7543e3836b1d3fc809e6bc4c3dbf2ac5a5fed770 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Mon, 22 Apr 2024 19:03:23 +0200 Subject: [PATCH 12/47] task #67: validate community name does not exist --- src/controllers/user_controller.cpp | 2 +- src/routes/user_routes.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 3523d87..8b8f946 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -125,7 +125,7 @@ void UserController::update_user_by_id(pqxx::connection &db, const crow::request void UserController::update_self(pqxx::connection &db, const crow::request &req, crow::response &res) { try { crow::json::rvalue update = crow::json::load(req.body); - user_id = update[id].s(); + std::string user_id = update["id"].s(); std::string temp_name = "", temp_pass = "", temp_email = ""; if (update.has("username")) { temp_name = update["username"].s(); diff --git a/src/routes/user_routes.cpp b/src/routes/user_routes.cpp index 4951853..e91c144 100755 --- a/src/routes/user_routes.cpp +++ b/src/routes/user_routes.cpp @@ -16,4 +16,8 @@ void initialize_user_routes(NebyApp& app, pqxx::connection& db) { CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { UserController::update_user_by_id(db, req, res, user_id); }); + + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + UserController::update_self(db, req, res); + }); } From 31af3a300e6e6bec56cd5c2fa89c52988d0b8f14 Mon Sep 17 00:00:00 2001 From: David Baque Date: Tue, 23 Apr 2024 17:31:41 +0200 Subject: [PATCH 13/47] task #99: refactor test setup --- test/tests/auth/login_test.cpp | 6 +- test/tests/auth/register_test.cpp | 96 ++++-------- test/tests/common.cpp | 16 +- test/tests/service_routes_test.cpp | 28 ---- test/tests/services/get_all_services_test.cpp | 69 ++++++++ test/tests/services/get_service_test.cpp | 27 ---- test/tests/services/get_services.md | 148 ++++++++++++------ 7 files changed, 204 insertions(+), 186 deletions(-) delete mode 100644 test/tests/service_routes_test.cpp diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp index 4d63bbd..3f4daff 100644 --- a/test/tests/auth/login_test.cpp +++ b/test/tests/auth/login_test.cpp @@ -82,7 +82,6 @@ TEST_F(LoginTest, correct_login) { EXPECT_TRUE(token_found); } -// Prueba de inicio de sesión con contraseña incorrecta TEST_F(LoginErrorsTest, incorrect_password) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; nlohmann::json post_data = { @@ -117,7 +116,7 @@ TEST_F(LoginErrorsTest, email_not_exists) { EXPECT_EQ(response.status_code, 404); } -TEST(LoginValidationInputTest, IncorrectEmail) { +TEST(LoginValidationInputTest, incorrect_email) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; // Lista de direcciones de correo electrónico incorrectas @@ -144,10 +143,9 @@ TEST(LoginValidationInputTest, IncorrectEmail) { } } -TEST(LoginValidationInputTest, Incorrect_Password) { +TEST(LoginValidationInputTest, incorrect_password) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_passwords = { "password", "12345678", "qwerty", "letmein", "abc123"}; diff --git a/test/tests/auth/register_test.cpp b/test/tests/auth/register_test.cpp index 50a561f..b19ec62 100644 --- a/test/tests/auth/register_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -9,42 +9,25 @@ #include "../common.h" -// Declaración de la función limpiarTablaUsers -void limpiarTablaUsers() { - try { - // Establecer la conexión a la base de datos - std::string connection_string = std::format("dbname={} user={} password={} host={} port={}", DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT); - pqxx::connection conn(connection_string); - // pqxx::connection conn("dbname=mydatabase user=myuser password=mypassword hostaddr=127.0.0.1 port=5432"); - - if (conn.is_open()) { - // Crear un objeto de transacción - pqxx::work txn(conn); - - // Ejecutar la consulta para limpiar la tabla users - txn.exec("DELETE FROM users"); - txn.exec("DELETE FROM communities"); - - // Confirmar la transacción - txn.commit(); - - } else { - std::cerr << "Error al conectar a la base de datos." << std::endl; - } - } catch (const std::exception& e) { - std::cerr << "Error de excepción: " << e.what() << std::endl; - } -} class RegisterValidations : public ::testing::Test { protected: void TearDown() override { clean_user_table(); + clean_community_table(); + } +}; + +class RegisterGeneralErrors : public ::testing::Test { + protected: + void TearDown() override { + clean_community_table(); + clean_user_table(); } }; // ** ---------- MISSING FIELDS ON REQ.BODY TESTS ---------- ** \\ -TEST(REGISTER_MISSING_FIELDS, MissingEmail) { +TEST(REGISTER_MISSING_FIELDS, missing_email) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -58,7 +41,7 @@ TEST(REGISTER_MISSING_FIELDS, MissingEmail) { EXPECT_EQ(response.status_code, 404) << "expect 404 status code with email missing"; } -TEST(REGISTER_MISSING_FIELDS, MissingUsername) { +TEST(REGISTER_MISSING_FIELDS, missing_username) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -72,7 +55,7 @@ TEST(REGISTER_MISSING_FIELDS, MissingUsername) { EXPECT_EQ(response.status_code, 404) << "expect 404 status code with username missing"; } -TEST(REGISTER_MISSING_FIELDS, MissingPassword) { +TEST(REGISTER_MISSING_FIELDS, missing_password) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -86,7 +69,7 @@ TEST(REGISTER_MISSING_FIELDS, MissingPassword) { EXPECT_EQ(response.status_code, 404) << "expect 404 status code with password missing"; } -TEST(REGISTER_MISSING_FIELDS, MissingType) { +TEST(REGISTER_MISSING_FIELDS, missing_type) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -100,7 +83,7 @@ TEST(REGISTER_MISSING_FIELDS, MissingType) { EXPECT_EQ(response.status_code, 404) << "expect 404 status code with type missing"; } -TEST(REGISTER_MISSING_FIELDS, MissingCommunityName) { +TEST(REGISTER_MISSING_FIELDS, missing_community_name) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -115,7 +98,7 @@ TEST(REGISTER_MISSING_FIELDS, MissingCommunityName) { EXPECT_EQ(response.status_code, 404) << "expect 404 status code with community_name missing"; } -TEST(REGISTER_MISSING_FIELDS, MissingCommunityCode) { +TEST(REGISTER_MISSING_FIELDS, missing_community_code) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json post_data = { @@ -132,10 +115,9 @@ TEST(REGISTER_MISSING_FIELDS, MissingCommunityCode) { // ** ---------- VALIDATION REQ.BODY FIELDS TESTS ---------- ** \\ -TEST_F(RegisterValidations, CorrectEmail) { +TEST_F(RegisterValidations, correct_email) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico correctas std::vector correct_emails = { "user@example.com", "user123@example.com", @@ -148,7 +130,6 @@ TEST_F(RegisterValidations, CorrectEmail) { "user@example.xyz"}; for (const auto& email : correct_emails) { - // Verificar que el correo electrónico sea correcto antes de enviar la solicitud nlohmann::json post_data = { {"email", email}, {"password", "F!sh1ngR0ck5"}, @@ -158,17 +139,16 @@ TEST_F(RegisterValidations, CorrectEmail) { auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - // Verificar que el servidor responda con el código de estado 201 (Created) EXPECT_EQ(response.status_code, 201) << "Expected 201 status code for correct email: " << email; - // Limpiar la tabla users después de cada prueba - limpiarTablaUsers(); + + clean_community_table(); + clean_user_table(); } } -TEST_F(RegisterValidations, IncorrectEmail) { +TEST_F(RegisterValidations, incorrect_email) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_emails = { "example%@gmail.com", "example@domain.", @@ -195,10 +175,9 @@ TEST_F(RegisterValidations, IncorrectEmail) { } } -TEST_F(RegisterValidations, Correct_password) { +TEST_F(RegisterValidations, correct_password) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_passwords = { "Tr0ub4dor&3", "P@ssw0rd!", @@ -217,14 +196,14 @@ TEST_F(RegisterValidations, Correct_password) { auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); EXPECT_EQ(response.status_code, 201) << "Expected 201 status code for incorrect password: " << password; - limpiarTablaUsers(); + clean_community_table(); + clean_user_table(); } } -TEST_F(RegisterValidations, Incorrect_Password) { +TEST_F(RegisterValidations, incorrect_password) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_passwords = { "password", "12345678", "qwerty", "letmein", "abc123"}; @@ -242,10 +221,9 @@ TEST_F(RegisterValidations, Incorrect_Password) { } } -TEST_F(RegisterValidations, Incorrect_Username) { +TEST_F(RegisterValidations, incorrect_username) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_usernames = { "Invalid!User", "SpacesUser ", @@ -267,10 +245,9 @@ TEST_F(RegisterValidations, Incorrect_Username) { } } -TEST_F(RegisterValidations, Correct_username) { +TEST_F(RegisterValidations, correct_username) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - // Lista de direcciones de correo electrónico incorrectas std::vector incorrect_usernames = { "SecureUser1", "Usuario123", @@ -289,11 +266,12 @@ TEST_F(RegisterValidations, Correct_username) { auto response = cpr::Post(cpr::Url{url}, cpr::Body{post_data.dump()}, cpr::Header{{"Content-Type", "application/json"}}); EXPECT_EQ(response.status_code, 201) << "Expected 201 status code for incorrect username: " << username; - limpiarTablaUsers(); + clean_community_table(); + clean_user_table(); } } -TEST_F(RegisterValidations, Incorrect_type) { +TEST_F(RegisterValidations, incorrect_type) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; std::string incorrect_type = "type_error"; @@ -312,14 +290,8 @@ TEST_F(RegisterValidations, Incorrect_type) { // ** ---------- GENERAL ERRORS TESTS ---------- ** \\ -class RegisterGeneralErrors : public ::testing::Test { - protected: - void TearDown() override { - limpiarTablaUsers(); - } -}; -TEST_F(RegisterGeneralErrors, UserAlredyExist) { +TEST_F(RegisterGeneralErrors, user_already_exists) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { @@ -363,7 +335,7 @@ TEST_F(RegisterGeneralErrors, UserAlredyExist) { EXPECT_EQ(response_username.status_code, 400); } -TEST_F(RegisterGeneralErrors, CorrectSignup) { +TEST_F(RegisterGeneralErrors, correct_signup) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { @@ -383,17 +355,13 @@ TEST_F(RegisterGeneralErrors, CorrectSignup) { std::string set_cookie_header = response.header["Set-Cookie"]; - // Buscar la cookie "token" dentro del encabezado "Set-Cookie" size_t token_pos = set_cookie_header.find("token="); bool token_found = token_pos != std::string::npos; - // Verificar que se encontró la cookie "token" EXPECT_TRUE(token_found); - clean_user_table(); - clean_community_table(); } -TEST_F(RegisterGeneralErrors, Community_Not_Exists) { +TEST_F(RegisterGeneralErrors, community_not_exists) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { diff --git a/test/tests/common.cpp b/test/tests/common.cpp index 81b3464..c562fa1 100644 --- a/test/tests/common.cpp +++ b/test/tests/common.cpp @@ -22,7 +22,6 @@ void clean_user_table() { pqxx::work txn(conn); txn.exec("DELETE FROM users"); - txn.exec("DELETE FROM communities"); txn.commit(); @@ -60,20 +59,17 @@ std::string create_user_test() { try { pqxx::work txn(conn); - // Sample user data (replace with your actual user data) std::string username = "test_user"; std::string email = "test@example.com"; std::string password = "password123"; std::string image_url = "https://example.com/image.jpg"; std::string type = "neighbor"; // or "admin" depending on the user's type - // Insert user into the database pqxx::result result = txn.exec_params("INSERT INTO users (username, email, password, image_url, type) VALUES ($1, $2, $3, $4, $5) RETURNING id", username, email, password, image_url, type); txn.commit(); - // Return the ID of the newly created user return result[0][0].as(); } catch (const std::exception& e) { std::cerr << "Error creating user: " << e.what() << std::endl; @@ -82,7 +78,7 @@ std::string create_user_test() { std::cerr << "Error connecting to the database." << std::endl; } - return ""; // Return empty string if user creation fails + return ""; } void create_services() { @@ -94,15 +90,12 @@ void create_services() { try { pqxx::work txn(conn); - // Create 5 example services for testing for (int i = 1; i <= 5; ++i) { - // Sample service data (replace with your actual service data) std::string title = "Service " + std::to_string(i); std::string description = "Description of service " + std::to_string(i); - int price = 100 * i; // Sample price - std::string type = "REQUESTED"; // or "OFFERED" depending on the service type + int price = 100 * i; + std::string type = "REQUESTED"; - // Insert service into the database txn.exec_params("INSERT INTO services (creator_id, title, description, price, type) VALUES ($1, $2, $3, $4, $5)", user_id, title, description, price, type); } @@ -140,7 +133,6 @@ std::string register_and_get_user_token() { return token_value; } else { - // La cookie "token" no se encontró en el encabezado - return ""; // Devolver un valor por defecto o manejar el error según sea necesario + return ""; } } diff --git a/test/tests/service_routes_test.cpp b/test/tests/service_routes_test.cpp deleted file mode 100644 index 4ef5287..0000000 --- a/test/tests/service_routes_test.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#include // Para std::getenv -#include -#include -#include -#include - -#include "common.h" - -class GetAllServices : public ::testing::Test { - protected: - void SetUp() override { - // Code here will be called immediately after the constructor (right - // before each test). - } - /* void TearDown() override { - clean_user_table(); - } */ -}; - -// Define your test cases within this class or other test fixture classes - -TEST_F(GetAllServices, first) { - // create_services(); - EXPECT_EQ(2, 2); -} diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index e69de29..b636d09 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include / +#include +#include +#include +#include + +#include "../common.h" + +class GetServicesTest : public testing::Test { + protected: + std::string token1_; + std::string _user_id1; + std::string _user_id2; + nlohmann::json user1_ = { + {"email", "example@gmail.com"}, + {"username", "username"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + nlohmann::json user2_ = { + {"email", "example2@gmail.com"}, + {"username", "username2"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + void SetUp() override { + // create user1 + // create user 2 + // create services for user1 + // create services for user2 + + } + + void TearDown() override { + // clear_user_table(); + // clear_community_table(); + // clear_services_table(); + } +}; + +// * void create_user_mock(nlohmann::json data); +//* void create_services_mock(std::string creator_id, int quantity); + +// * crear usuario y obtener su token, no crear servicios y solo existe una comunidad +// ? testear que devuelve un 200 y un servies array vacio + +// * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas +// ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: + +TEST(GetServicesTest, first) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("services")); + + ASSERT_TRUE(json["services"].is_array()); + + clean_user_table(); + clean_community_table(); +} diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index 8871b8e..e69de29 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -1,27 +0,0 @@ -#include -#include - -#include // Para std::getenv -#include -#include -#include -#include - -#include "../common.h" - -TEST(GET_SERVICES, first) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; - - auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); - - EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; - - auto json = nlohmann::json::parse(response.text); - - ASSERT_TRUE(json.contains("services")); - - ASSERT_TRUE(json["services"].is_array()); - - clean_user_table(); - clean_community_table(); -} diff --git a/test/tests/services/get_services.md b/test/tests/services/get_services.md index 8a0168c..719cfc5 100644 --- a/test/tests/services/get_services.md +++ b/test/tests/services/get_services.md @@ -5,65 +5,111 @@ This document outlines the testing scenarios for the `get_services` endpoint in ## Test Scenarios 1. **Valid Request** - - **Description:** Test the endpoint with a valid request from an authenticated user. - - **Inputs:** - - Valid JWT token via cookies. - - User ID in the request body. - - Community ID associated with the user. - - **Expected Output:** - - HTTP status code: 200. - - JSON response containing an array of services belonging to the user's community. + + - **Description:** Test the endpoint with a valid request from an authenticated user. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 200. + - JSON response containing an array of services belonging to the user's community. 2. **Valid Request with Status Filter** - - **Description:** Test the endpoint with a valid request including a status filter. - - **Inputs:** - - Valid JWT token via cookies. - - User ID in the request body. - - Community ID associated with the user. - - Status parameter in the query string (`open` or `closed`). - - **Expected Output:** - - HTTP status code: 200. - - JSON response containing an array of services belonging to the user's community with the specified status. + + - **Description:** Test the endpoint with a valid request including a status filter. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - Status parameter in the query string (`open` or `closed`). + - **Expected Output:** + - HTTP status code: 200. + - JSON response containing an array of services belonging to the user's community with the specified status. 3. **Invalid Status Parameter** - - **Description:** Test the endpoint with an invalid status parameter. - - **Inputs:** - - Valid JWT token via cookies. - - User ID in the request body. - - Community ID associated with the user. - - Invalid status parameter in the query string. - - **Expected Output:** - - HTTP status code: 400. - - Error message indicating that the status parameter is not a valid value. + + - **Description:** Test the endpoint with an invalid status parameter. + - **Inputs:** + - Valid JWT token via cookies. + - User ID in the request body. + - Community ID associated with the user. + - Invalid status parameter in the query string. + - **Expected Output:** + - HTTP status code: 400. + - Error message indicating that the status parameter is not a valid value. 4. **Unauthorized Access** - - **Description:** Test the endpoint without providing a JWT token. - - **Inputs:** - - No JWT token provided. - - User ID in the request body. - - Community ID associated with the user. - - **Expected Output:** - - HTTP status code: 401. - - Error message indicating unauthorized access. + + - **Description:** Test the endpoint without providing a JWT token. + - **Inputs:** + - No JWT token provided. + - User ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 401. + - Error message indicating unauthorized access. 5. **Invalid User ID** - - **Description:** Test the endpoint with an invalid or missing user ID. - - **Inputs:** - - Valid JWT token via cookies. - - Invalid or missing user ID in the request body. - - Community ID associated with the user. - - **Expected Output:** - - HTTP status code: 400. - - Error message indicating an invalid or missing user ID. + + - **Description:** Test the endpoint with an invalid or missing user ID. + - **Inputs:** + - Valid JWT token via cookies. + - Invalid or missing user ID in the request body. + - Community ID associated with the user. + - **Expected Output:** + - HTTP status code: 400. + - Error message indicating an invalid or missing user ID. 6. **Internal Server Error** - - **Description:** Test the endpoint when an internal server error occurs during processing. - - **Inputs:** - - Valid JWT token via cookies. - - Valid user ID in the request body. - - Community ID associated with the user. - - Trigger an internal server error within the endpoint. - - **Expected Output:** - - HTTP status code: 500. - - Error message indicating an internal server error. + - **Description:** Test the endpoint when an internal server error occurs during processing. + - **Inputs:** + - Valid JWT token via cookies. + - Valid user ID in the request body. + - Community ID associated with the user. + - Trigger an internal server error within the endpoint. + - **Expected Output:** + - HTTP status code: 500. + - Error message indicating an internal server error. + + +```json +{ + "id": "service_uuidv4", + "creator_id": "creator_uuidv4", + "title": "service_title", + "description": "service_description", + "price": "service_price", + "status": "service_status ('open' | 'closed')", + "type": "service_type ('offered' | 'requested')", + "image_url": "optional", + "creator": { + "id": "creator_uuidv4", + "username": "username", + "type": "('admin' | 'neighbor')", + "email": "email@gmail.com", + "balance": 1000, + "created_at": "date", + "updated_at": "date" + }, + "bueyer_id": "optional", + "buyer": { + "id:": "buyer_uuidv4", + "username": "username", + "type": "('admin' | 'neighbor')", + "email": "email@gmail.com", + "balance": 1000, + "created_at": "date", + "updated_at": "date" + } +} + +``` + +- `image_url` is optional field +- `buyer_id` and `buyer` are optional fields + +crear usuario y que tenga 3 servicios, +testear que todo va bien ya que hay toekn valido, +el json de respuesta tiene "services" y no es vacio, se comprueba todas su propiedades \ No newline at end of file From f9bf4786351956b9dc2829605d0c1e2072012e32 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Tue, 23 Apr 2024 18:51:49 +0200 Subject: [PATCH 14/47] task #126: update from admin by id --- src/controllers/user_controller.cpp | 67 ++++++++++++++++++----------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 8b8f946..cbb4fad 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -88,34 +88,53 @@ void UserController::delete_user_by_id(pqxx::connection &db, crow::response &res void UserController::update_user_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &user_id) { try { - bool userFound = UserModel::exists_id(db, user_id); - if (userFound) { - crow::json::rvalue update = crow::json::load(req.body); - std::string temp_name = "", temp_pass = "", temp_email = ""; - if (update.has("username")) { - temp_name = update["username"].s(); - if (!validate_username(temp_name, res)) return; + crow::json::rvalue update = crow::json::load(req.body); + if (update["isAdmin"].b() == true) { + if (user_id.empty()) { + handle_error(res, "id must be provided", 400); + return; } - if (update.has("email")) { - temp_email = update["email"].s(); - if (!validate_email(temp_email, res)) return; + + std::unique_ptr user = UserModel::get_user_by_id(db, user_id); + + if (!user) { + handle_error(res, "user not found", 404); + return; } - if (update.has("password")) { - temp_pass = update["password"].s(); - if (!validate_password(temp_pass, res)) return; + + std::string user_community = user.get()->get_community_id(); + + std::unique_ptr admin = UserModel::get_user_by_id(db, update["id"].s()); + std::string admin_community = admin.get()->get_community_id(); + + if (user_community == admin_community) { + crow::json::rvalue update = crow::json::load(req.body); + std::string temp_name = "", temp_pass = "", temp_email = ""; + if (update.has("username")) { + temp_name = update["username"].s(); + if (!validate_username(temp_name, res)) return; + } + if (update.has("email")) { + temp_email = update["email"].s(); + if (!validate_email(temp_email, res)) return; + } + if (update.has("password")) { + temp_pass = update["password"].s(); + if (!validate_password(temp_pass, res)) return; + } + std::string hash = BCrypt::generateHash(temp_pass); + bool succes = UserModel::update_user_by_id(db, user_id, temp_name, temp_email, hash); + if (succes) { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "User updated successfully"; + res.write(response_message.dump()); + res.end(); + } else + handle_error(res, "internal server error", 500); } - std::string hash = BCrypt::generateHash(temp_pass); - bool succes = UserModel::update_user_by_id(db, user_id, temp_name, temp_email, hash); - if (succes) { - res.code = 200; - crow::json::wvalue response_message; - response_message["message"] = "User updated successfully"; - res.write(response_message.dump()); - res.end(); - } else - handle_error(res, "internal server error", 500); } else - handle_error(res, "user not found", 404); + handle_error(res, "not enough privileges", 403); } catch (const std::exception &e) { std::cerr << "Error updating user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); From ed2fa455c5d7aa077c1cf470bd84a6cf831a12f6 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Tue, 23 Apr 2024 23:29:26 +0200 Subject: [PATCH 15/47] task #128: delete service by id --- include/controllers/service_controller.h | 4 +- include/models/service_model.h | 2 + src/controllers/service_controller.cpp | 46 +++++++++++++++++++++++ src/models/service_model.cpp | 47 ++++++++++++++++++++++++ src/routes/service_routes.cpp | 4 ++ 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index ebf0aa5..a6cebc6 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include #include @@ -10,10 +11,10 @@ // ** custom includes #include #include +#include #include #include #include -#include // ** --------------------------------------------- class ServiceController { @@ -24,4 +25,5 @@ class ServiceController { static void create_service(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_services(pqxx::connection& db, const crow::request& req, crow::response& res); + static void delete_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); }; diff --git a/include/models/service_model.h b/include/models/service_model.h index 3b3b1e5..224e115 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -45,6 +45,8 @@ class ServiceModel { UserModel get_buyer() const; static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); + static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); static std::vector> get_services(pqxx::connection& db, const std::string& community_id, const std::string& status = ""); + static bool delete_service_by_id(pqxx::connection& db, const std::string id); }; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 487bc81..7379a14 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -122,3 +122,49 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & handle_error(res, "internal server error", 500); } } + +void ServiceController::delete_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { + try { + crow::json::rvalue request = crow::json::load(req.body); + if (request["isAdmin"].b() == true) { + if (service_id.empty()) { + handle_error(res, "id must be provided", 400); + return; + } + + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); + + if (!service) { + handle_error(res, "service not found", 404); + return; + } + + std::string service_creator_id = service.get()->get_creator_id(); + + std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); + std::string service_community = creator.get()->get_community_id(); + + std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); + std::string admin_community = admin.get()->get_community_id(); + + if (service_community == admin_community) { + bool deleted = ServiceModel::delete_service_by_id(db, service_id); + + if (deleted) { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "service deleted successfully"; + res.write(response_message.dump()); + res.end(); + } else + handle_error(res, "service not found", 404); + } else { + handle_error(res, "service not in your community", 400); + return; + } + } + } catch (const std::exception &e) { + std::cerr << "Error deleting service: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} \ No newline at end of file diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index df7b155..0758e53 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -164,3 +164,50 @@ std::vector> ServiceModel::get_services(pqxx::conn return all_services; } + +std::unique_ptr get_service(pqxx::connection& db, const std::string& column, const std::string& value, bool throw_when_null) { + pqxx::work txn(db); + pqxx::result result = txn.exec_params(std::format("SELECT * FROM users WHERE {} = $1", column), value); + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("service not found"); + else + return nullptr; + } + + return std::make_unique( + result[0]["id"].as(), + result[0]["creator_id"].as(), + result[0]["buyer_id"].as(), + result[0]["tittle"].as(), + result[0]["description"].as(), + result[0]["price"].as(), + result[0]["service_status"].as(), + result[0]["service_type"].as(), + result[0]["image_url"].as(), + result[0]["created_at"].as(), + result[0]["updated_at"].as()); +} + +std::unique_ptr ServiceModel::get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null) { + return get_service(db, "id", id, throw_when_null); +} + +bool ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string id) { + try { + pqxx::work txn(db); + + pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING id", id); + + txn.commit(); + + if (!result.empty()) return true; + + return false; + } catch (const std::exception& e) { + std::cerr << "Failed to delete service: " << e.what() << std::endl; + return false; + } +} \ No newline at end of file diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 80a6b55..6d5d3fd 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -18,4 +18,8 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** PUT /api/services/:id // ** DELETE /api/services/:id + + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, std::string& service_id) { + ServiceController::delete_service(db, req, res, service_id); + }); } From 11ec96f9919ac09ce46743f1aefa81ff4699dfde Mon Sep 17 00:00:00 2001 From: Voooigt Date: Wed, 24 Apr 2024 14:06:05 +0200 Subject: [PATCH 16/47] task#128: delete service by id --- src/routes/service_routes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 6d5d3fd..92da6ce 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -19,7 +19,7 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** DELETE /api/services/:id - CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, std::string& service_id) { + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { ServiceController::delete_service(db, req, res, service_id); }); } From bbc39a9274982e15bd2816d704b93b5b19e48826 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Wed, 24 Apr 2024 20:31:36 +0200 Subject: [PATCH 17/47] task#128: falta testing --- include/models/service_model.h | 4 ++- src/controllers/service_controller.cpp | 12 +++---- src/models/service_model.cpp | 49 +++++++++++++++++++------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/include/models/service_model.h b/include/models/service_model.h index 224e115..c8f9c23 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -30,6 +30,8 @@ class ServiceModel { ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer); + ServiceModel(std::string id); + std::string get_id() const; std::string get_creator_id() const; std::optional get_buyer_id() const; @@ -48,5 +50,5 @@ class ServiceModel { static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); static std::vector> get_services(pqxx::connection& db, const std::string& community_id, const std::string& status = ""); - static bool delete_service_by_id(pqxx::connection& db, const std::string id); + static std::unique_ptr delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null = false); }; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 7379a14..da361a4 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -135,11 +135,12 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); if (!service) { - handle_error(res, "service not found", 404); + handle_error(res, "service no.t found", 404); return; } std::string service_creator_id = service.get()->get_creator_id(); + std::cout << service_creator_id << std::endl; std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); std::string service_community = creator.get()->get_community_id(); @@ -148,13 +149,10 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request std::string admin_community = admin.get()->get_community_id(); if (service_community == admin_community) { - bool deleted = ServiceModel::delete_service_by_id(db, service_id); + std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); - if (deleted) { - res.code = 200; - crow::json::wvalue response_message; - response_message["message"] = "service deleted successfully"; - res.write(response_message.dump()); + if (deleted_service) { + res.code = 204; res.end(); } else handle_error(res, "service not found", 404); diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index 0758e53..ad40dd9 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -4,6 +4,8 @@ ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at), _creator(creator), _buyer(buyer) {} +ServiceModel::ServiceModel(std::string id) : _id(id) {} + std::string ServiceModel::get_id() const { return _id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } std::optional ServiceModel::get_buyer_id() const { return _buyer_id; } @@ -167,7 +169,7 @@ std::vector> ServiceModel::get_services(pqxx::conn std::unique_ptr get_service(pqxx::connection& db, const std::string& column, const std::string& value, bool throw_when_null) { pqxx::work txn(db); - pqxx::result result = txn.exec_params(std::format("SELECT * FROM users WHERE {} = $1", column), value); + pqxx::result result = txn.exec_params(std::format("SELECT * FROM services WHERE {} = $1", column), value); txn.commit(); if (result.empty()) { @@ -176,17 +178,35 @@ std::unique_ptr get_service(pqxx::connection& db, const std::strin else return nullptr; } - + std::cout << "id: " << result[0]["id"].as() << std::endl; + std::cout << "creator_id: " << result[0]["creator_id"].as() << std::endl; + std::cout << "title: " << result[0]["title"].as() << std::endl; + std::cout << "description: " << result[0]["description"].as() << std::endl; + std::cout << "price: " << result[0]["price"].as() << std::endl; + std::cout << "status: " << result[0]["status"].as() << std::endl; + std::cout << "type: " << result[0]["type"].as() << std::endl; + std::cout << "created_at: " << result[0]["created_at"].as() << std::endl; + std::cout << "updated_at: " << result[0]["updated_at"].as() << std::endl; + std::optional buyer_id_field; + std::optional image_url_field; + if (!result[0]["buyer_id"].is_null()) + buyer_id_field = result[0]["buyer_id"].as(); + else + buyer_id_field = std::nullopt; + if (!result[0]["image_url"].is_null()) + image_url_field = result[0]["image_url"].as(); + else + image_url_field = std::nullopt; return std::make_unique( result[0]["id"].as(), result[0]["creator_id"].as(), - result[0]["buyer_id"].as(), - result[0]["tittle"].as(), + buyer_id_field, + result[0]["title"].as(), result[0]["description"].as(), result[0]["price"].as(), - result[0]["service_status"].as(), - result[0]["service_type"].as(), - result[0]["image_url"].as(), + result[0]["status"].as(), + result[0]["type"].as(), + image_url_field, result[0]["created_at"].as(), result[0]["updated_at"].as()); } @@ -195,7 +215,7 @@ std::unique_ptr ServiceModel::get_service_by_id(pqxx::connection& return get_service(db, "id", id, throw_when_null); } -bool ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string id) { +std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null) { try { pqxx::work txn(db); @@ -203,11 +223,16 @@ bool ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string txn.commit(); - if (!result.empty()) return true; + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("service not found"); + else + return nullptr; + } + return std::make_unique(result[0]["id"].as()); - return false; } catch (const std::exception& e) { std::cerr << "Failed to delete service: " << e.what() << std::endl; - return false; + return nullptr; } -} \ No newline at end of file +} From 14d7c2b1ff9b08eafa041678d9e46bb68b4942f9 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Thu, 25 Apr 2024 03:33:23 +0200 Subject: [PATCH 18/47] task#128: el testing no ha funcionado --- src/controllers/service_controller.cpp | 46 +++-- test/tests/common.cpp | 8 +- test/tests/services/delete_service_test.cpp | 172 ++++++++++++++++++ test/tests/services/get_all_services_test.cpp | 3 +- 4 files changed, 198 insertions(+), 31 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index da361a4..a3cb2c0 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -126,41 +126,37 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & void ServiceController::delete_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { try { crow::json::rvalue request = crow::json::load(req.body); - if (request["isAdmin"].b() == true) { - if (service_id.empty()) { - handle_error(res, "id must be provided", 400); - return; - } - - std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); + // if (request["isAdmin"].b() == true) { + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); - if (!service) { - handle_error(res, "service no.t found", 404); - return; - } + if (!service) { + handle_error(res, "service not found", 404); + return; + } - std::string service_creator_id = service.get()->get_creator_id(); - std::cout << service_creator_id << std::endl; + std::string service_creator_id = service.get()->get_creator_id(); - std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); - std::string service_community = creator.get()->get_community_id(); + std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); + std::string service_community = creator.get()->get_community_id(); - std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); - std::string admin_community = admin.get()->get_community_id(); + std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); + std::string admin_community = admin.get()->get_community_id(); - if (service_community == admin_community) { - std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); + if ((service_community == admin_community && request["isAdmin"].b() == true) || service_creator_id == request["id"].s()) { + std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); - if (deleted_service) { - res.code = 204; - res.end(); - } else - handle_error(res, "service not found", 404); + if (deleted_service) { + res.code = 204; + res.end(); } else { - handle_error(res, "service not in your community", 400); + handle_error(res, "service not found", 404); return; } + } else { + handle_error(res, "user without admin privileges or not creator of service", 403); + return; } + } catch (const std::exception &e) { std::cerr << "Error deleting service: " << e.what() << std::endl; handle_error(res, "internal server error", 500); diff --git a/test/tests/common.cpp b/test/tests/common.cpp index c562fa1..048da80 100644 --- a/test/tests/common.cpp +++ b/test/tests/common.cpp @@ -78,7 +78,7 @@ std::string create_user_test() { std::cerr << "Error connecting to the database." << std::endl; } - return ""; + return ""; } void create_services() { @@ -93,8 +93,8 @@ void create_services() { for (int i = 1; i <= 5; ++i) { std::string title = "Service " + std::to_string(i); std::string description = "Description of service " + std::to_string(i); - int price = 100 * i; - std::string type = "REQUESTED"; + int price = 100 * i; + std::string type = "REQUESTED"; txn.exec_params("INSERT INTO services (creator_id, title, description, price, type) VALUES ($1, $2, $3, $4, $5)", user_id, title, description, price, type); @@ -133,6 +133,6 @@ std::string register_and_get_user_token() { return token_value; } else { - return ""; + return ""; } } diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index e69de29..e71f43e 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "../common.h" +/* + +TEST if user not authentication with jwt +status_code = 401 + + */ + +TEST(DeleteServiceAuth, delete_service_not_auth) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/uuidexample"; + + auto response = cpr::Delete(cpr::Url{url}); + EXPECT_EQ(response.status_code, 404); + auto json = nlohmann::json::parse(response.text); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not token provided"); +} + +/* + +TEST if user is authenticated but not is admin and not creator +status_code = 401 +res.body = 'not enough priviligies'; + + // 1. crear un usuario admin + // 1.1 admin creado, crea un servicio y guardamos el id + // 2. obtener el codigo de la comunidad + // 3. crear un usuario neighbor + // 4. token neighbor + // 5. haces la peticion de eliminar el primer sericio + */ + +/* + TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community + so + status = 403 + res.body = user without admin privileges or not creator of service + */ +class DeleteServiceAdminBAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _admin1_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _admin1_id_; + std::string _admin_community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_admin1() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example1@gmail.com"}, + {"username", "example1"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name1"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin1_token_ = token_value; + } else { + _admin1_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin1_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/service"; + nlohmann::json new_user = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + register_admin1(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteServiceAdminBAuth, delete_service_AdminB) { + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/service/" + _service_id_; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin1_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for incorrect password: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "incorrect password"); +} +/* + TEST if user is auth, not admin, but the user is creator of service + status_code = 204 + */ + +/* + +TEST if user is admin and auth but service not found +status_code = 404 +res.body = 'service not found' + */ + +/* +TEST if user is admin and auth,and service exists +status_code = 204 +res.body = '' + */ + +/* +TEST if user is admin and auth, but the user isn't the creator +status_code = 204 + */ + +/* +TEST if user not admin but auth, and the user is the creator +status_code = 204 + */ \ No newline at end of file diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index b636d09..93c29ea 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -28,11 +28,10 @@ class GetServicesTest : public testing::Test { {"community_name", "example_community_name"}}; void SetUp() override { - // create user1 + // create user1 // create user 2 // create services for user1 // create services for user2 - } void TearDown() override { From a07663d4eb18791638721af48adc25d0f181e2e8 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Sat, 27 Apr 2024 00:23:21 +0200 Subject: [PATCH 19/47] task#128: test neither --- src/controllers/service_controller.cpp | 5 +- test/tests/services/delete_service_test.cpp | 258 ++++++++++++++++++-- 2 files changed, 247 insertions(+), 16 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index a3cb2c0..fe2c872 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -145,8 +145,11 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request if ((service_community == admin_community && request["isAdmin"].b() == true) || service_creator_id == request["id"].s()) { std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); - if (deleted_service) { + if (true) { res.code = 204; + crow::json::wvalue response_message; + response_message["message"] = "service deleted successfully"; + res.write(response_message.dump()); res.end(); } else { handle_error(res, "service not found", 404); diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index e71f43e..a577e40 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include // Para std::getenv #include @@ -38,6 +39,121 @@ res.body = 'not enough priviligies'; // 4. token neighbor // 5. haces la peticion de eliminar el primer sericio */ +class DeleteServiceNeitherAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteServiceNeitherAuth, delete_service_Neither) { + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); +} /* TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community @@ -52,7 +168,6 @@ class DeleteServiceAdminBAuth : public testing::Test { std::string _admin_token_; std::string _admin_id_; std::string _admin1_id_; - std::string _admin_community_id_; void register_admin() { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; @@ -85,16 +200,15 @@ class DeleteServiceAdminBAuth : public testing::Test { void register_admin1() { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - nlohmann::json new_user = { - {"email", "example1@gmail.com"}, - {"username", "example1"}, + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, {"password", "P@ssw0rd!"}, {"type", "admin"}, - {"community_name", "example_community_name1"}}; + {"community_name", "example_community_namee"}}; auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - + auto json = nlohmann::json::parse(response.text); std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); @@ -107,23 +221,22 @@ class DeleteServiceAdminBAuth : public testing::Test { } else { _admin1_token_ = ""; } - - auto json = nlohmann::json::parse(response.text); + // auto json = nlohmann::json::parse(response.text); _admin1_id_ = json["id"]; } void SetUp() override { register_admin(); - - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/service"; - nlohmann::json new_user = { + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { {"title", "nodeberiaexistir"}, {"description", "some description 555555555"}, {"price", 40}, {"type", "offered"}}; - auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; + sleep(1); register_admin1(); } @@ -135,19 +248,134 @@ class DeleteServiceAdminBAuth : public testing::Test { }; TEST_F(DeleteServiceAdminBAuth, delete_service_AdminB) { - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/service/" + _service_id_; + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin1_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for incorrect password: "; + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "incorrect password"); + EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); } /* TEST if user is auth, not admin, but the user is creator of service status_code = 204 */ +class DeleteServiceCreatorAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + std::cout << _service_id_ << std::endl; + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteServiceCreatorAuth, delete_service_creator) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 204) << "Expected 403 status code for user deleted succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "user deleted succesfully"); +} /* TEST if user is admin and auth but service not found From 882b3bc0cd6332beb7ae9dc1db3ec56c06de3707 Mon Sep 17 00:00:00 2001 From: voooigt Date: Sat, 27 Apr 2024 04:07:26 +0200 Subject: [PATCH 20/47] task#128: delete service by id complete --- src/controllers/service_controller.cpp | 21 ++- test/tests/services/delete_service_test.cpp | 178 ++++++++++++++++++-- 2 files changed, 176 insertions(+), 23 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index fe2c872..64c910b 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -127,9 +127,10 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request try { crow::json::rvalue request = crow::json::load(req.body); // if (request["isAdmin"].b() == true) { - std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); - if (!service) { + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id, false); + + if (service == nullptr) { handle_error(res, "service not found", 404); return; } @@ -143,16 +144,14 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request std::string admin_community = admin.get()->get_community_id(); if ((service_community == admin_community && request["isAdmin"].b() == true) || service_creator_id == request["id"].s()) { - std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); - - if (true) { - res.code = 204; - crow::json::wvalue response_message; - response_message["message"] = "service deleted successfully"; - res.write(response_message.dump()); + std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); + if (deleted_service) { + crow::json::wvalue message({{"message", "service deleted succesfully"}}); + res.write(message.dump()); + res.code = 200; res.end(); } else { - handle_error(res, "service not found", 404); + handle_error(res, "could not delete service", 400); return; } } else { @@ -162,6 +161,6 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request } catch (const std::exception &e) { std::cerr << "Error deleting service: " << e.what() << std::endl; - handle_error(res, "internal server error", 500); + handle_error(res, "Error deleting service", 500); } } \ No newline at end of file diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index a577e40..9a676e3 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -357,7 +357,6 @@ class DeleteServiceCreatorAuth : public testing::Test { auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - std::cout << _service_id_ << std::endl; } void TearDown() override { @@ -370,11 +369,11 @@ class DeleteServiceCreatorAuth : public testing::Test { TEST_F(DeleteServiceCreatorAuth, delete_service_creator) { sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 204) << "Expected 403 status code for user deleted succesfully: "; + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; EXPECT_TRUE(json.contains("message")); - EXPECT_EQ(json["message"], "user deleted succesfully"); + EXPECT_EQ(json["message"], "service deleted succesfully"); } /* @@ -382,19 +381,174 @@ TEST if user is admin and auth but service not found status_code = 404 res.body = 'service not found' */ +class DeleteServiceNotFoundAuth : public testing::Test { + protected: + std::string _admin_token_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + } + + void SetUp() override { + register_admin(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; +TEST_F(DeleteServiceNotFoundAuth, delete_service_not_found) { + std::string lol = "123e4567-e89b-12d3-a456-426655440000"; + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + lol; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 404) << "Expected 404 status code for service not found: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "service not found"); +} /* TEST if user is admin and auth,and service exists status_code = 204 res.body = '' */ +class DeleteServiceAdminAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; -/* -TEST if user is admin and auth, but the user isn't the creator -status_code = 204 - */ + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; -/* -TEST if user not admin but auth, and the user is the creator -status_code = 204 - */ \ No newline at end of file + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteServiceAdminAuth, delete_service_admin) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "service deleted succesfully"); +} \ No newline at end of file From 4fe7af97db7a449119838bc8341b9ccb627dfa8f Mon Sep 17 00:00:00 2001 From: JayexDesigns Date: Sat, 27 Apr 2024 16:06:49 +0200 Subject: [PATCH 21/47] task #128: small fixes and formatting applied --- include/controllers/service_controller.h | 1 - include/models/service_model.h | 7 +++---- src/controllers/service_controller.cpp | 7 +++---- src/models/service_model.cpp | 2 -- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index a6cebc6..560acc6 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include diff --git a/include/models/service_model.h b/include/models/service_model.h index c8f9c23..2023f92 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -2,7 +2,6 @@ #include #include - #include #include #include @@ -30,8 +29,6 @@ class ServiceModel { ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer); - ServiceModel(std::string id); - std::string get_id() const; std::string get_creator_id() const; std::optional get_buyer_id() const; @@ -47,8 +44,10 @@ class ServiceModel { UserModel get_buyer() const; static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); - static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); static std::vector> get_services(pqxx::connection& db, const std::string& community_id, const std::string& status = ""); + + static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); + static std::unique_ptr delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null = false); }; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 64c910b..b8a57fe 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -126,10 +126,9 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & void ServiceController::delete_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { try { crow::json::rvalue request = crow::json::load(req.body); - // if (request["isAdmin"].b() == true) { std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id, false); - + if (service == nullptr) { handle_error(res, "service not found", 404); return; @@ -144,7 +143,7 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request std::string admin_community = admin.get()->get_community_id(); if ((service_community == admin_community && request["isAdmin"].b() == true) || service_creator_id == request["id"].s()) { - std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); + std::unique_ptr deleted_service = ServiceModel::delete_service_by_id(db, service_id); if (deleted_service) { crow::json::wvalue message({{"message", "service deleted succesfully"}}); res.write(message.dump()); @@ -163,4 +162,4 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request std::cerr << "Error deleting service: " << e.what() << std::endl; handle_error(res, "Error deleting service", 500); } -} \ No newline at end of file +} diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index ad40dd9..a596ff9 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -4,8 +4,6 @@ ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at), _creator(creator), _buyer(buyer) {} -ServiceModel::ServiceModel(std::string id) : _id(id) {} - std::string ServiceModel::get_id() const { return _id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } std::optional ServiceModel::get_buyer_id() const { return _buyer_id; } From df5a777b68c5107f88b57ea536a2f739891238e6 Mon Sep 17 00:00:00 2001 From: JayexDesigns Date: Sat, 27 Apr 2024 18:50:49 +0200 Subject: [PATCH 22/47] task #132: applied formatting rules --- README.md | 2 +- include/controllers/user_controller.h | 1 - include/models/user_model.h | 3 +- src/controllers/auth_controller.cpp | 1 - src/controllers/user_controller.cpp | 2 +- test/TEST_README.md | 22 +++++++++------ test/tests/auth/{lgoin.md => login.md} | 0 test/tests/auth/login_test.cpp | 4 +-- test/tests/auth/register_test.cpp | 4 +-- test/tests/common.cpp | 1 - test/tests/common.h | 1 - test/tests/main.cpp | 1 - test/tests/services/create_service.md | 1 - test/tests/services/delete_service_test.cpp | 28 +++++++++---------- test/tests/services/get_all_services_test.cpp | 2 -- test/tests/services/get_services.md | 2 +- test/tests/test_one.cpp | 4 +-- 17 files changed, 34 insertions(+), 45 deletions(-) rename test/tests/auth/{lgoin.md => login.md} (100%) diff --git a/README.md b/README.md index 5cc5d5d..af5d0a8 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Backend -[TEST](test/TEST_README.md) \ No newline at end of file +[TEST](test/TEST_README.md) diff --git a/include/controllers/user_controller.h b/include/controllers/user_controller.h index 4483da1..f03e85e 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -5,7 +5,6 @@ #include #include #include - #include #include #include diff --git a/include/models/user_model.h b/include/models/user_model.h index c6c6a11..0221b51 100755 --- a/include/models/user_model.h +++ b/include/models/user_model.h @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include @@ -13,7 +12,7 @@ class UserModel { private: std::string _id; std::string _community_id; - std::string _username; + std::string _username; std::string _email; std::string _type; int _balance; diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index 8a28351..2490b54 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -1,7 +1,6 @@ #include #include #include - #include #include // Include the ctime header for time functions #include diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 8b8f946..8e7fc19 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -153,4 +153,4 @@ void UserController::update_self(pqxx::connection &db, const crow::request &req, std::cerr << "Error updating user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); } -} \ No newline at end of file +} diff --git a/test/TEST_README.md b/test/TEST_README.md index 850b7bb..4b7ac69 100644 --- a/test/TEST_README.md +++ b/test/TEST_README.md @@ -1,14 +1,20 @@ # Integration Testing for C++ Server ## Table of Contents -1. [Project Description](#project-description) -2. [Tools Used](#tools-used) -3. [Project Objective](#project-objective) -4. [Project Structure](#project-structure) -5. [Running the Tests](#running-the-tests) - - [Method 1: Automated Execution with Docker](#method-1-automated-execution-with-docker) - - [Method 2: Manual Execution within the Container](#method-2-manual-execution-within-the-container) -6. [Tests Organization](#tests-organization) +- [Integration Testing for C++ Server](#integration-testing-for-c-server) + - [Table of Contents](#table-of-contents) + - [Project Description](#project-description) + - [Tools Used](#tools-used) + - [Project Objective](#project-objective) + - [Project Structure](#project-structure) + - [Running the Tests](#running-the-tests) + - [Method 1: Automated Execution with Docker](#method-1-automated-execution-with-docker) + - [Method 2: Manual Execution within the Container](#method-2-manual-execution-within-the-container) + - [Tests Organization](#tests-organization) + - [Auth](#auth) + - [Users](#users) + - [Services](#services) + - [Notifications](#notifications) ## Project Description diff --git a/test/tests/auth/lgoin.md b/test/tests/auth/login.md similarity index 100% rename from test/tests/auth/lgoin.md rename to test/tests/auth/login.md diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp index 3f4daff..740f7a7 100644 --- a/test/tests/auth/login_test.cpp +++ b/test/tests/auth/login_test.cpp @@ -1,12 +1,10 @@ #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" class LoginTest : public testing::Test { @@ -192,4 +190,4 @@ TEST(LoginValidationInputTest, missing_password) { EXPECT_EQ(response.status_code, 404); EXPECT_TRUE(json.contains("error")); EXPECT_EQ(json["error"], "missing password field"); -} \ No newline at end of file +} diff --git a/test/tests/auth/register_test.cpp b/test/tests/auth/register_test.cpp index b19ec62..21b6c6a 100644 --- a/test/tests/auth/register_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -1,12 +1,10 @@ #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" class RegisterValidations : public ::testing::Test { @@ -380,4 +378,4 @@ TEST_F(RegisterGeneralErrors, community_not_exists) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; EXPECT_EQ(error_message_username, "community does not exists"); -} \ No newline at end of file +} diff --git a/test/tests/common.cpp b/test/tests/common.cpp index 048da80..0ec0f31 100644 --- a/test/tests/common.cpp +++ b/test/tests/common.cpp @@ -1,5 +1,4 @@ #include "common.h" - #include // Para std::getenv #include #include diff --git a/test/tests/common.h b/test/tests/common.h index 235f5d4..e0450a7 100644 --- a/test/tests/common.h +++ b/test/tests/common.h @@ -2,7 +2,6 @@ #define COMMON_HPP #include - #include #include diff --git a/test/tests/main.cpp b/test/tests/main.cpp index 926358d..a115f6f 100644 --- a/test/tests/main.cpp +++ b/test/tests/main.cpp @@ -1,5 +1,4 @@ #include - #include int main(int argc, char** argv) { diff --git a/test/tests/services/create_service.md b/test/tests/services/create_service.md index d10026f..bc8f837 100644 --- a/test/tests/services/create_service.md +++ b/test/tests/services/create_service.md @@ -28,4 +28,3 @@ This document outlines the testing scenarios for the `create_service` endpoint i - **Expected Output:** - HTTP status code: 500. - Error message indicating an internal server error. - diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index 9a676e3..6322053 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -1,20 +1,18 @@ #include #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" /* TEST if user not authentication with jwt status_code = 401 - */ +*/ TEST(DeleteServiceAuth, delete_service_not_auth) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/uuidexample"; @@ -38,7 +36,7 @@ res.body = 'not enough priviligies'; // 3. crear un usuario neighbor // 4. token neighbor // 5. haces la peticion de eliminar el primer sericio - */ +*/ class DeleteServiceNeitherAuth : public testing::Test { protected: std::string _service_id_; @@ -156,11 +154,11 @@ TEST_F(DeleteServiceNeitherAuth, delete_service_Neither) { } /* - TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community - so - status = 403 - res.body = user without admin privileges or not creator of service - */ +TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community +so +status = 403 +res.body = user without admin privileges or not creator of service +*/ class DeleteServiceAdminBAuth : public testing::Test { protected: std::string _service_id_; @@ -257,9 +255,9 @@ TEST_F(DeleteServiceAdminBAuth, delete_service_AdminB) { EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); } /* - TEST if user is auth, not admin, but the user is creator of service - status_code = 204 - */ +TEST if user is auth, not admin, but the user is creator of service +status_code = 204 +*/ class DeleteServiceCreatorAuth : public testing::Test { protected: std::string _service_id_; @@ -380,7 +378,7 @@ TEST_F(DeleteServiceCreatorAuth, delete_service_creator) { TEST if user is admin and auth but service not found status_code = 404 res.body = 'service not found' - */ +*/ class DeleteServiceNotFoundAuth : public testing::Test { protected: std::string _admin_token_; @@ -436,7 +434,7 @@ TEST_F(DeleteServiceNotFoundAuth, delete_service_not_found) { TEST if user is admin and auth,and service exists status_code = 204 res.body = '' - */ +*/ class DeleteServiceAdminAuth : public testing::Test { protected: std::string _service_id_; @@ -551,4 +549,4 @@ TEST_F(DeleteServiceAdminAuth, delete_service_admin) { EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; EXPECT_TRUE(json.contains("message")); EXPECT_EQ(json["message"], "service deleted succesfully"); -} \ No newline at end of file +} diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index 93c29ea..725e78f 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -1,12 +1,10 @@ #include #include - #include / #include #include #include #include - #include "../common.h" class GetServicesTest : public testing::Test { diff --git a/test/tests/services/get_services.md b/test/tests/services/get_services.md index 719cfc5..55e49d2 100644 --- a/test/tests/services/get_services.md +++ b/test/tests/services/get_services.md @@ -112,4 +112,4 @@ This document outlines the testing scenarios for the `get_services` endpoint in crear usuario y que tenga 3 servicios, testear que todo va bien ya que hay toekn valido, -el json de respuesta tiene "services" y no es vacio, se comprueba todas su propiedades \ No newline at end of file +el json de respuesta tiene "services" y no es vacio, se comprueba todas su propiedades diff --git a/test/tests/test_one.cpp b/test/tests/test_one.cpp index 1ed0154..64b146b 100644 --- a/test/tests/test_one.cpp +++ b/test/tests/test_one.cpp @@ -1,12 +1,10 @@ #include #include - #include // Para std::getenv #include #include #include - int sum(int a, int b) { return a + b; } @@ -96,4 +94,4 @@ std::string simulateLoginAndGetToken() { ASSERT_TRUE(user.contains("type")); } } - */ \ No newline at end of file + */ From babd9ca4424967198356afe30ff5c7f016d52adb Mon Sep 17 00:00:00 2001 From: JayexDesigns Date: Sat, 27 Apr 2024 21:09:45 +0200 Subject: [PATCH 23/47] task #132: solved bug in return value of delete service by id --- include/models/service_model.h | 12 ++++----- src/models/service_model.cpp | 46 ++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/include/models/service_model.h b/include/models/service_model.h index 2023f92..67b20d1 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -19,15 +19,13 @@ class ServiceModel { std::string _status; std::string _type; std::optional _image_url; + std::optional _creator; + std::optional _buyer; std::string _created_at; std::string _updated_at; - UserModel _creator; - UserModel _buyer; public: - ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); - - ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer); + ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::optional creator, std::optional buyer, std::string created_at, std::string updated_at); std::string get_id() const; std::string get_creator_id() const; @@ -40,8 +38,8 @@ class ServiceModel { std::optional get_image_url() const; std::string get_created_at() const; std::string get_updated_at() const; - UserModel get_creator() const; - UserModel get_buyer() const; + std::optional get_creator() const; + std::optional get_buyer() const; static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index a596ff9..cc09752 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -1,8 +1,6 @@ #include -ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at) {} - -ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at), _creator(creator), _buyer(buyer) {} +ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::optional creator, std::optional buyer, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _creator(creator), _buyer(buyer), _created_at(created_at), _updated_at(updated_at) {} std::string ServiceModel::get_id() const { return _id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } @@ -13,10 +11,10 @@ int ServiceModel::get_price() const { return _price; } std::string ServiceModel::get_status() const { return _status; } std::string ServiceModel::get_type() const { return _type; } std::optional ServiceModel::get_image_url() const { return _image_url; } +std::optional ServiceModel::get_creator() const { return _creator; }; +std::optional ServiceModel::get_buyer() const { return _buyer; }; std::string ServiceModel::get_created_at() const { return _created_at; } std::string ServiceModel::get_updated_at() const { return _updated_at; } -UserModel ServiceModel::get_creator() const { return _creator; }; -UserModel ServiceModel::get_buyer() const { return _buyer; }; std::unique_ptr ServiceModel::create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow) { pqxx::work txn(db); @@ -53,6 +51,8 @@ std::unique_ptr ServiceModel::create_service(pqxx::connection& db, result[0]["status"].as(), result[0]["type"].as(), image_url_field, + std::nullopt, + std::nullopt, result[0]["created_at"].as(), result[0]["updated_at"].as()); } @@ -156,10 +156,10 @@ std::vector> ServiceModel::get_services(pqxx::conn row["status"].as(), row["type"].as(), image_url_field, - row["created_at"].as(), - row["updated_at"].as(), creator, - buyer)); + buyer, + row["created_at"].as(), + row["updated_at"].as())); } return all_services; @@ -205,6 +205,8 @@ std::unique_ptr get_service(pqxx::connection& db, const std::strin result[0]["status"].as(), result[0]["type"].as(), image_url_field, + std::nullopt, + std::nullopt, result[0]["created_at"].as(), result[0]["updated_at"].as()); } @@ -217,7 +219,7 @@ std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connectio try { pqxx::work txn(db); - pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING id", id); + pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING *", id); txn.commit(); @@ -227,8 +229,32 @@ std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connectio else return nullptr; } - return std::make_unique(result[0]["id"].as()); + std::optional buyer_id_field; + std::optional image_url_field; + if (!result[0]["buyer_id"].is_null()) + buyer_id_field = result[0]["buyer_id"].as(); + else + buyer_id_field = std::nullopt; + if (!result[0]["image_url"].is_null()) + image_url_field = result[0]["image_url"].as(); + else + image_url_field = std::nullopt; + + return std::make_unique( + result[0]["id"].as(), + result[0]["creator_id"].as(), + buyer_id_field, + result[0]["title"].as(), + result[0]["description"].as(), + result[0]["price"].as(), + result[0]["status"].as(), + result[0]["type"].as(), + image_url_field, + std::nullopt, + std::nullopt, + result[0]["created_at"].as(), + result[0]["updated_at"].as()); } catch (const std::exception& e) { std::cerr << "Failed to delete service: " << e.what() << std::endl; return nullptr; From 651d0eca18acb6e3af473bef5dec1e00f3ddcef1 Mon Sep 17 00:00:00 2001 From: JayexDesigns Date: Sat, 27 Apr 2024 22:30:44 +0200 Subject: [PATCH 24/47] task #132: applied optional values from service model to controller --- src/controllers/service_controller.cpp | 46 ++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index b8a57fe..e2f7aab 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -75,19 +75,8 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["id"] = all_services[i].get()->get_id(); service["creator_id"] = all_services[i].get()->get_creator_id(); - if (all_services[i].get()->get_buyer_id().has_value()) { - crow::json::wvalue buyer; - buyer["id"] = all_services[i].get()->get_buyer().get_id(); - buyer["username"] = all_services[i].get()->get_buyer().get_username(); - buyer["type"] = all_services[i].get()->get_buyer().get_type(); - buyer["email"] = all_services[i].get()->get_buyer().get_email(); - buyer["balance"] = all_services[i].get()->get_buyer().get_balance(); - buyer["created_at"] = all_services[i].get()->get_buyer().get_created_at(); - buyer["updated_at"] = all_services[i].get()->get_buyer().get_updated_at(); - - service["buyer"] = crow::json::wvalue(buyer); + if (all_services[i].get()->get_buyer_id().has_value()) service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); - } service["title"] = all_services[i].get()->get_title(); service["description"] = all_services[i].get()->get_description(); service["price"] = all_services[i].get()->get_price(); @@ -98,16 +87,31 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["created_at"] = all_services[i].get()->get_created_at(); service["updated_at"] = all_services[i].get()->get_updated_at(); - crow::json::wvalue creator; - creator["id"] = all_services[i].get()->get_creator().get_id(); - creator["username"] = all_services[i].get()->get_creator().get_username(); - creator["type"] = all_services[i].get()->get_creator().get_type(); - creator["email"] = all_services[i].get()->get_creator().get_email(); - creator["balance"] = all_services[i].get()->get_creator().get_balance(); - creator["created_at"] = all_services[i].get()->get_creator().get_created_at(); - creator["updated_at"] = all_services[i].get()->get_creator().get_updated_at(); + if (all_services[i].get()->get_creator().has_value()) { + crow::json::wvalue creator; + creator["id"] = all_services[i].get()->get_creator().value().get_id(); + creator["username"] = all_services[i].get()->get_creator().value().get_username(); + creator["type"] = all_services[i].get()->get_creator().value().get_type(); + creator["email"] = all_services[i].get()->get_creator().value().get_email(); + creator["balance"] = all_services[i].get()->get_creator().value().get_balance(); + creator["created_at"] = all_services[i].get()->get_creator().value().get_created_at(); + creator["updated_at"] = all_services[i].get()->get_creator().value().get_updated_at(); + + service["creator"] = crow::json::wvalue(creator); + } - service["creator"] = crow::json::wvalue(creator); + if (all_services[i].get()->get_buyer().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = all_services[i].get()->get_buyer().value().get_id(); + buyer["username"] = all_services[i].get()->get_buyer().value().get_username(); + buyer["type"] = all_services[i].get()->get_buyer().value().get_type(); + buyer["email"] = all_services[i].get()->get_buyer().value().get_email(); + buyer["balance"] = all_services[i].get()->get_buyer().value().get_balance(); + buyer["created_at"] = all_services[i].get()->get_buyer().value().get_created_at(); + buyer["updated_at"] = all_services[i].get()->get_buyer().value().get_updated_at(); + + service["buyer"] = crow::json::wvalue(buyer); + } services.push_back(service); } From 0e5e8bf860bfbbec9ba0c54a54bccd817d4e4327 Mon Sep 17 00:00:00 2001 From: Sxnter Date: Mon, 29 Apr 2024 11:49:33 +0200 Subject: [PATCH 25/47] task #118: create /auth/self endpoint --- include/controllers/auth_controller.h | 2 ++ src/controllers/auth_controller.cpp | 30 +++++++++++++++++++++++++++ src/routes/auth_routes.cpp | 6 ++++++ 3 files changed, 38 insertions(+) diff --git a/include/controllers/auth_controller.h b/include/controllers/auth_controller.h index 0270997..a399a2f 100755 --- a/include/controllers/auth_controller.h +++ b/include/controllers/auth_controller.h @@ -5,6 +5,7 @@ #include #include #include + #include #include #include @@ -14,4 +15,5 @@ class AuthController { public: static void register_user(pqxx::connection& db, const crow::request& req, crow::response& res); static void login_user(pqxx::connection& db, const crow::request& req, crow::response& res); + static void get_self(pqxx::connection& db, const crow::request& req, crow::response& res); }; diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index 2490b54..2684ea7 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -1,6 +1,7 @@ #include #include #include + #include #include // Include the ctime header for time functions #include @@ -142,3 +143,32 @@ void AuthController::login_user(pqxx::connection &db, const crow::request &req, handle_error(res, "INTERNAL SERVER ERROR", 500); } } +void get_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { + crow::json::rvalue body = crow::json::load(req.body); + std::string user_id = body["id"].s(); + std::unique_ptr user = UserModel::get_user_by_id(db, user_id); + if (!user) { + handle_error(res, "user not found", 404); + return; + } + crow::json::wvalue user_data; + user_data["id"] = user.get()->get_id(); + user_data["community_id"] = user.get()->get_community_id(); + user_data["username"] = user.get()->get_username(); + user_data["email"] = user.get()->get_email(); + user_data["type"] = user.get()->get_type(); + user_data["balance"] = user.get()->get_balance(); + user_data["created_at"] = user.get()->get_created_at(); + user_data["updated_at"] = user.get()->get_updated_at(); + + crow::json::wvalue data{{"user", user_data}}; + res.code = 200; + res.write(data.dump()); + res.end(); + + } catch (const std::exception &e) { + std::cerr << "Error in get self: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} diff --git a/src/routes/auth_routes.cpp b/src/routes/auth_routes.cpp index 50e3506..263c70f 100755 --- a/src/routes/auth_routes.cpp +++ b/src/routes/auth_routes.cpp @@ -10,4 +10,10 @@ void initialize_auth_routes(NebyApp& app, pqxx::connection& db) { .methods(crow::HTTPMethod::POST)([&db](const crow::request& req, crow::response& res) { AuthController::login_user(db, req, res); }); + + CROW_ROUTE(app, "/api/auth/self") + .methods(crow::HTTPMethod::GET) + .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + AuthController::get_self(db, req, res); + }); } From 633dc1852a20b4af58f4a28ccad2aca508b1b303 Mon Sep 17 00:00:00 2001 From: JayexDesigns Date: Mon, 29 Apr 2024 15:17:43 +0200 Subject: [PATCH 26/47] task #118: small fix on controller method name --- src/controllers/auth_controller.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index 2684ea7..911c473 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -1,7 +1,6 @@ #include #include #include - #include #include // Include the ctime header for time functions #include @@ -143,15 +142,18 @@ void AuthController::login_user(pqxx::connection &db, const crow::request &req, handle_error(res, "INTERNAL SERVER ERROR", 500); } } -void get_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + +void AuthController::get_self(pqxx::connection &db, const crow::request &req, crow::response &res) { try { crow::json::rvalue body = crow::json::load(req.body); std::string user_id = body["id"].s(); std::unique_ptr user = UserModel::get_user_by_id(db, user_id); + if (!user) { handle_error(res, "user not found", 404); return; } + crow::json::wvalue user_data; user_data["id"] = user.get()->get_id(); user_data["community_id"] = user.get()->get_community_id(); From 7f8d7e397454e4a7799838510e590a771bc3d436 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Tue, 30 Apr 2024 20:46:07 +0200 Subject: [PATCH 27/47] task#126: update user from admin --- include/controllers/user_controller.h | 1 + include/models/user_model.h | 2 + include/utils/validations.h | 4 + src/controllers/user_controller.cpp | 32 +- src/models/user_model.cpp | 13 + src/utils/validations.cpp | 8 + test/tests/services/delete_service_test.cpp | 8 +- test/tests/services/get_all_services_test.cpp | 4 +- test/tests/users/update_user_admin_test.cpp | 433 ++++++++++++++++++ 9 files changed, 487 insertions(+), 18 deletions(-) create mode 100644 test/tests/users/update_user_admin_test.cpp diff --git a/include/controllers/user_controller.h b/include/controllers/user_controller.h index f03e85e..4483da1 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -5,6 +5,7 @@ #include #include #include + #include #include #include diff --git a/include/models/user_model.h b/include/models/user_model.h index 0221b51..29b3228 100755 --- a/include/models/user_model.h +++ b/include/models/user_model.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include #include @@ -48,4 +49,5 @@ class UserModel { static bool delete_user_by_id(pqxx::connection& db, const std::string& id); static bool update_user_by_id(pqxx::connection& db, const std::string& id, const std::string username = "", const std::string email = "", const std::string password = ""); + static bool update_user_admin(pqxx::connection& db, const std::string& id, const std::string username, const int balance); }; diff --git a/include/utils/validations.h b/include/utils/validations.h index 9a82d70..e05888e 100755 --- a/include/utils/validations.h +++ b/include/utils/validations.h @@ -2,4 +2,8 @@ #include +#include + bool validate_required_body_fields(const crow::json::rvalue &body, const std::vector &required_fields, crow::response &res); + +bool isValidUUID(const std::string &uuid); \ No newline at end of file diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 4d9ad77..8f04a78 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -95,6 +95,11 @@ void UserController::update_user_by_id(pqxx::connection &db, const crow::request return; } + if (!isValidUUID(user_id)) { + handle_error(res, "invalid id", 400); + return; + } + std::unique_ptr user = UserModel::get_user_by_id(db, user_id); if (!user) { @@ -109,21 +114,24 @@ void UserController::update_user_by_id(pqxx::connection &db, const crow::request if (user_community == admin_community) { crow::json::rvalue update = crow::json::load(req.body); - std::string temp_name = "", temp_pass = "", temp_email = ""; + std::string temp_name = ""; + int temp_balance = -1; if (update.has("username")) { temp_name = update["username"].s(); - if (!validate_username(temp_name, res)) return; - } - if (update.has("email")) { - temp_email = update["email"].s(); - if (!validate_email(temp_email, res)) return; + // validate username currently throws an error, so this return and error messages ar not being used + if (!validate_username(temp_name, res)) { + handle_error(res, "incorrect username", 400); + return; + }; } - if (update.has("password")) { - temp_pass = update["password"].s(); - if (!validate_password(temp_pass, res)) return; + if (update.has("balance")) { + temp_balance = update["balance"].i(); + if (temp_balance < 0) { + handle_error(res, "invalid balance", 400); + return; + } } - std::string hash = BCrypt::generateHash(temp_pass); - bool succes = UserModel::update_user_by_id(db, user_id, temp_name, temp_email, hash); + bool succes = UserModel::update_user_admin(db, user_id, temp_name, temp_balance); if (succes) { res.code = 200; crow::json::wvalue response_message; @@ -132,6 +140,8 @@ void UserController::update_user_by_id(pqxx::connection &db, const crow::request res.end(); } else handle_error(res, "internal server error", 500); + } else { + handle_error(res, "not enough privileges", 403); } } else handle_error(res, "not enough privileges", 403); diff --git a/src/models/user_model.cpp b/src/models/user_model.cpp index b3efdbe..7783336 100755 --- a/src/models/user_model.cpp +++ b/src/models/user_model.cpp @@ -160,3 +160,16 @@ bool UserModel::update_user_by_id(pqxx::connection& db, const std::string& id, c return false; } } + +bool UserModel::update_user_admin(pqxx::connection& db, const std::string& id, const std::string username, const int balance) { + try { + pqxx::work txn(db); + if (username != "") pqxx::result result = txn.exec_params("UPDATE users SET username = $1 WHERE id = $2", username, id); + if (!(balance < 0)) pqxx::result result = txn.exec_params("UPDATE users SET balance = $1 WHERE id = $2", balance, id); + txn.commit(); + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to update user: " << e.what() << std::endl; + return false; + } +} diff --git a/src/utils/validations.cpp b/src/utils/validations.cpp index 8e83bdd..80583ac 100755 --- a/src/utils/validations.cpp +++ b/src/utils/validations.cpp @@ -13,3 +13,11 @@ bool validate_required_body_fields(const crow::json::rvalue &body, const std::ve return true; } + +bool isValidUUID(const std::string &uuid) { + // Regular expression pattern for UUID format with lowercase hexadecimal characters + std::regex pattern("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + + // Check if the string matches the pattern + return std::regex_match(uuid, pattern); +} \ No newline at end of file diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index 6322053..0c8b5a1 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -1,11 +1,13 @@ #include #include #include + #include // Para std::getenv #include #include #include #include + #include "../common.h" /* @@ -30,12 +32,6 @@ TEST if user is authenticated but not is admin and not creator status_code = 401 res.body = 'not enough priviligies'; - // 1. crear un usuario admin - // 1.1 admin creado, crea un servicio y guardamos el id - // 2. obtener el codigo de la comunidad - // 3. crear un usuario neighbor - // 4. token neighbor - // 5. haces la peticion de eliminar el primer sericio */ class DeleteServiceNeitherAuth : public testing::Test { protected: diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index 725e78f..8d466b1 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -1,10 +1,12 @@ #include #include -#include / + +#include #include #include #include #include + #include "../common.h" class GetServicesTest : public testing::Test { diff --git a/test/tests/users/update_user_admin_test.cpp b/test/tests/users/update_user_admin_test.cpp new file mode 100644 index 0000000..3776487 --- /dev/null +++ b/test/tests/users/update_user_admin_test.cpp @@ -0,0 +1,433 @@ +#include +#include +#include + +#include // Para std::getenv +#include +#include +#include +#include + +#include "../common.h" + +/* + +TEST if user not authentication with jwt +status_code = 401 + +*/ +TEST(UpdateAdminAuth, update_admin_not_auth) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/123"; + + auto response = cpr::Delete(cpr::Url{url}); + EXPECT_EQ(response.status_code, 404); + auto json = nlohmann::json::parse(response.text); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not token provided"); +} + +/* + +TEST if user is authenticated but is not admin +status_code = 401 +res.body = 'not enough priviligies'; + +*/ +class UpdateAdminNotAdmin : public testing::Test { + protected: + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateAdminNotAdmin, update_admin_not_admin) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for not enough privileges: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not enough privileges"); +} + +/* + +TEST if user is authenticated but and admin but invalid Id (doesnt follow the psql UUID format) +status_code = 400 +res.body = 'invalid id'; + +*/ + +class UpdateAdminInvalidId : public testing::Test { + protected: + std::string _admin_token_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + } + + void SetUp() override { + register_admin(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateAdminInvalidId, update_admin_invalid_id) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + "123"; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid id"); +} + +/* + +TEST if user is authenticated and admin but neighbour is not found +status_code = 404 +res.body = 'user not found'; + +*/ + +class UpdateAdminNotFound : public testing::Test { + protected: + std::string _admin_token_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + } + + void SetUp() override { + register_admin(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateAdminNotFound, update_admin_not_found) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + "d39f6c52-5de7-44b6-9ea4-87ccb1055097"; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 404) << "Expected 404 status code for uer not found: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user not found"); +} + +/* + +TEST if user is authenticated and admin but from a diferent community +status_code = 403 +res.body = 'not enough privileges'; + +*/ + +class UpdateAdminCommunityB : public testing::Test { + protected: + std::string _service_id_; + std::string _admin1_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _admin1_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_admin1() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_namee"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin1_token_ = token_value; + } else { + _admin1_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _admin1_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + sleep(1); + register_admin1(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +/* + +TEST if user is authenticated and admin but new username or balance is invalid +status_code = 400 +res.body = 'invalid username' / 'invlaid balance'; + +*/ + +class UpdateAdminInvalidUsername : public testing::Test { + protected: + std::string _admin_token_; + std::string _admin_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_username) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; + nlohmann::json update = { + {"username", "1"}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid username: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "incorrect username"); +} + +TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_balance) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; + nlohmann::json update = { + {"balance", "-1"}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid balance: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid balance"); +} + +TEST_F(UpdateAdminInvalidUsername, update_admin_succes) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; + nlohmann::json update = { + {"balance", "1"}, + {"username", "example"}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user updated succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "User updated successfully"); +} \ No newline at end of file From 6f53246684abff63cffacdca4b56329aac89c1cd Mon Sep 17 00:00:00 2001 From: Voooigt Date: Tue, 30 Apr 2024 21:04:38 +0200 Subject: [PATCH 28/47] task#126: applied formating --- include/controllers/auth_controller.h | 1 - include/models/user_model.h | 1 - include/utils/validations.h | 3 +-- src/utils/validations.cpp | 2 +- test/tests/services/delete_service_test.cpp | 2 -- test/tests/services/get_all_services_test.cpp | 2 -- test/tests/users/update_user_admin_test.cpp | 4 +--- 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/include/controllers/auth_controller.h b/include/controllers/auth_controller.h index a399a2f..d46f9b1 100755 --- a/include/controllers/auth_controller.h +++ b/include/controllers/auth_controller.h @@ -5,7 +5,6 @@ #include #include #include - #include #include #include diff --git a/include/models/user_model.h b/include/models/user_model.h index 29b3228..b879528 100755 --- a/include/models/user_model.h +++ b/include/models/user_model.h @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include diff --git a/include/utils/validations.h b/include/utils/validations.h index e05888e..269c467 100755 --- a/include/utils/validations.h +++ b/include/utils/validations.h @@ -1,9 +1,8 @@ #pragma once #include - #include bool validate_required_body_fields(const crow::json::rvalue &body, const std::vector &required_fields, crow::response &res); -bool isValidUUID(const std::string &uuid); \ No newline at end of file +bool isValidUUID(const std::string &uuid); diff --git a/src/utils/validations.cpp b/src/utils/validations.cpp index 80583ac..5144681 100755 --- a/src/utils/validations.cpp +++ b/src/utils/validations.cpp @@ -20,4 +20,4 @@ bool isValidUUID(const std::string &uuid) { // Check if the string matches the pattern return std::regex_match(uuid, pattern); -} \ No newline at end of file +} diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index 0c8b5a1..e0fde10 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -1,13 +1,11 @@ #include #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" /* diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index 8d466b1..1c19207 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -1,12 +1,10 @@ #include #include - #include #include #include #include #include - #include "../common.h" class GetServicesTest : public testing::Test { diff --git a/test/tests/users/update_user_admin_test.cpp b/test/tests/users/update_user_admin_test.cpp index 3776487..5723ede 100644 --- a/test/tests/users/update_user_admin_test.cpp +++ b/test/tests/users/update_user_admin_test.cpp @@ -1,13 +1,11 @@ #include #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" /* @@ -430,4 +428,4 @@ TEST_F(UpdateAdminInvalidUsername, update_admin_succes) { EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user updated succesfully: "; EXPECT_TRUE(json.contains("message")); EXPECT_EQ(json["message"], "User updated successfully"); -} \ No newline at end of file +} From 89ff0dc12beaf7f7ca798b478c3981db4766c8f4 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Wed, 1 May 2024 00:54:52 +0200 Subject: [PATCH 29/47] task #136: update service by id not tested --- include/controllers/service_controller.h | 1 + include/models/service_model.h | 2 + src/controllers/service_controller.cpp | 62 ++++++++++++++++++++++++ src/models/service_model.cpp | 14 ++++++ src/routes/service_routes.cpp | 4 ++ 5 files changed, 83 insertions(+) diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index 560acc6..2e52feb 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -25,4 +25,5 @@ class ServiceController { static void create_service(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_services(pqxx::connection& db, const crow::request& req, crow::response& res); static void delete_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); + static void update_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); }; diff --git a/include/models/service_model.h b/include/models/service_model.h index 67b20d1..70d4061 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -48,4 +48,6 @@ class ServiceModel { static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); static std::unique_ptr delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null = false); + + static bool update_service_by_id(pqxx::connection& db, const std::string id, const std::string tittle, const std::string description, const int price); }; diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index e2f7aab..67bc241 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -167,3 +167,65 @@ void ServiceController::delete_service(pqxx::connection &db, const crow::request handle_error(res, "Error deleting service", 500); } } + +void ServiceController::update_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { + try { + crow::json::rvalue request = crow::json::load(req.body); + + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id, false); + + if (service == nullptr) { + handle_error(res, "service not found", 404); + return; + } + + std::string service_creator_id = service.get()->get_creator_id(); + + std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); + std::string service_community = creator.get()->get_community_id(); + + std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); + std::string admin_community = admin.get()->get_community_id(); + + if ((service_community == admin_community && request["isAdmin"].b() == true) || service_creator_id == request["id"].s()) { + // comprobacions + crow::json::rvalue update = crow::json::load(req.body); + std::string temp_tittle = "", temp_description = ""; + int temp_price = -1; + + if (update.has("tittle")) { + temp_tittle = update["tittle"].s(); + } + + if (update.has("description")) { + temp_description = update["description"].s(); + } + + if (update.has("price")) { + temp_price = update["price"].i(); + if (temp_price < 0) { + handle_error(res, "invalid price", 400); + return; + } + } + + bool updated_service = ServiceModel::update_service_by_id(db, service_id, temp_tittle, temp_description, temp_price); + if (updated_service) { + crow::json::wvalue message({{"message", "service deleted succesfully"}}); + res.write(message.dump()); + res.code = 200; + res.end(); + } else { + handle_error(res, "could not delete service", 400); + return; + } + } else { + handle_error(res, "user without admin privileges or not creator of service", 403); + return; + } + + } catch (const std::exception &e) { + std::cerr << "Error deleting service: " << e.what() << std::endl; + handle_error(res, "Error deleting service", 500); + } +} diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index cc09752..c6c726b 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -260,3 +260,17 @@ std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connectio return nullptr; } } + +bool ServiceModel::update_service_by_id(pqxx::connection& db, const std::string id, const std::string tittle, const std::string description, const int price) { + try { + pqxx::work txn(db); + if (tittle != "") pqxx::result result = txn.exec_params("UPDATE services SET title = $1 WHERE id = $2", tittle, id); + if (description != "") pqxx::result result = txn.exec_params("UPDATE services SET description = $1 WHERE id = $2", description, id); + if (!(price < 0)) pqxx::result result = txn.exec_params("UPDATE services SET price = $1 WHERE id = $2", price, id); + txn.commit(); + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to update service: " << e.what() << std::endl; + return false; + } +} diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 92da6ce..8c783a9 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -17,6 +17,10 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** PUT /api/services/:id + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { + ServiceController::update_service(db, req, res, service_id); + }); + // ** DELETE /api/services/:id CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { From c1dfe748bbdc9e690c59feac87e146f30e1b9da3 Mon Sep 17 00:00:00 2001 From: Voooigt Date: Wed, 1 May 2024 19:40:20 +0200 Subject: [PATCH 30/47] task #136: all tests passed --- test/tests/services/delete_service_test.cpp | 2 +- test/tests/services/update_service_test.cpp | 572 ++++++++++++++++++++ 2 files changed, 573 insertions(+), 1 deletion(-) diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index e0fde10..3752cb3 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -27,7 +27,7 @@ TEST(DeleteServiceAuth, delete_service_not_auth) { /* TEST if user is authenticated but not is admin and not creator -status_code = 401 +status_code = 403 res.body = 'not enough priviligies'; */ diff --git a/test/tests/services/update_service_test.cpp b/test/tests/services/update_service_test.cpp index e69de29..7653d47 100644 --- a/test/tests/services/update_service_test.cpp +++ b/test/tests/services/update_service_test.cpp @@ -0,0 +1,572 @@ +#include +#include +#include +#include // Para std::getenv +#include +#include +#include +#include +#include "../common.h" +/* + +TEST if user not authentication with jwt +status_code = 401 + +*/ + +TEST(UpdateServiceNotAuth, update_service_not_auth) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/uuidexample"; + + auto response = cpr::Put(cpr::Url{url}); + EXPECT_EQ(response.status_code, 404); + auto json = nlohmann::json::parse(response.text); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not token provided"); +} + +/* + +TEST if user is authenticated but not is admin and not creator +status_code = 403 +res.body = 'not enough priviligies'; + +*/ +class UpdateServiceNeitherAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateServiceNeitherAuth, update_service_Neither) { + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); +} + +/* +TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community +so +status = 403 +res.body = user without admin privileges or not creator of service +*/ +class UpdateServiceAdminBAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _admin1_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _admin1_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_admin1() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_namee"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin1_token_ = token_value; + } else { + _admin1_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _admin1_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + sleep(1); + register_admin1(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateServiceAdminBAuth, update_service_AdminB) { + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin1_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); +} + +/* + +TEST if user is admin and auth but service not found +status_code = 404 +res.body = 'service not found' +*/ +class UpdateServiceNotFoundAuth : public testing::Test { + protected: + std::string _admin_token_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + } + + void SetUp() override { + register_admin(); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateServiceNotFoundAuth, update_service_not_found) { + std::string lol = "123e4567-e89b-12d3-a456-426655440000"; + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + lol; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 404) << "Expected 404 status code for service not found: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "service not found"); +} + +/* +TEST if user is auth, not admin, but the user is creator of service +status_code = 204 +*/ +class UpdateServiceCreatorAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "sideberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateServiceCreatorAuth, update_service_creator) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + nlohmann::json update = { + {"price", 1}, + {"tittle", "example"}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "service deleted succesfully"); +} + +/* +TEST if user is admin and auth,and service exists but price is invalid +status_code = 400 +res.body = 'invalid price' +*/ +TEST_F(UpdateServiceCreatorAuth, update_service_invalid_price) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + nlohmann::json update = { + {"price", -11}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 200 status code for invalid price: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid price"); +} + +/* +TEST if user is admin and auth,and service exists +status_code = 204 +res.body = '' +*/ +class UpdateServiceAdminAuth : public testing::Test { + protected: + std::string _service_id_; + std::string _neighbor_token_; + std::string _admin_token_; + std::string _admin_id_; + std::string _neighbor_id_; + std::string _community_id_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + + void register_neighbor(std::string code) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _neighbor_token_ = token_value; + } else { + _neighbor_token_ = ""; + } + // auto json = nlohmann::json::parse(response.text); + _neighbor_id_ = json["id"]; + } + + void SetUp() override { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); + + txn.commit(); + + _community_id_ = result[0][0].as(); + } catch (const std::exception& e) { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } else { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(_community_id_); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateServiceAdminAuth, update_service_admin) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; + nlohmann::json update = { + {"price", 1}, + {"tittle", "example"}}; + auto response = cpr::Put(cpr::Url{url_service}, cpr::Body{update.dump()}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "service deleted succesfully"); +} From fa4d895650f4c43729b6f0cf7c57f8b86787a645 Mon Sep 17 00:00:00 2001 From: leobelab Date: Wed, 1 May 2024 20:29:53 +0200 Subject: [PATCH 31/47] task #127: service by id -first commit --- include/controllers/service_controller.h | 2 + include/models/service_model.h | 12 +- src/controllers/service_controller.cpp | 118 +++- src/controllers/user_controller.cpp | 2 +- src/models/service_model.cpp | 47 +- src/routes/service_routes.cpp | 4 + test/tests/auth/register_test.cpp | 4 +- test/tests/services/delete_service_test.cpp | 554 ------------------ test/tests/services/get_all_services_test.cpp | 4 +- 9 files changed, 147 insertions(+), 600 deletions(-) diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index 560acc6..c65b5c9 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include #include @@ -24,5 +25,6 @@ class ServiceController { static void create_service(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_services(pqxx::connection& db, const crow::request& req, crow::response& res); + static void get_service_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id); static void delete_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); }; diff --git a/include/models/service_model.h b/include/models/service_model.h index 2023f92..67b20d1 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -19,15 +19,13 @@ class ServiceModel { std::string _status; std::string _type; std::optional _image_url; + std::optional _creator; + std::optional _buyer; std::string _created_at; std::string _updated_at; - UserModel _creator; - UserModel _buyer; public: - ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at); - - ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer); + ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::optional creator, std::optional buyer, std::string created_at, std::string updated_at); std::string get_id() const; std::string get_creator_id() const; @@ -40,8 +38,8 @@ class ServiceModel { std::optional get_image_url() const; std::string get_created_at() const; std::string get_updated_at() const; - UserModel get_creator() const; - UserModel get_buyer() const; + std::optional get_creator() const; + std::optional get_buyer() const; static std::unique_ptr create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow = false); diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index b8a57fe..749f9c3 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -50,6 +50,76 @@ void ServiceController::create_service(pqxx::connection &db, const crow::request } } +void ServiceController::get_service_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &service_id) { + try { + if (service_id.empty()) { + handle_error(res, "id must be provided", 400); + return; + } + // Consulta el servicio por su ID + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); + + // Verifica si se encontró el servicio + if (!service) { + handle_error(res, "service not found", 404); + return; + } + + // Construye el objeto JSON del servicio + crow::json::wvalue service_json; + + service_json["id"] = service.get()->get_id(); + service_json["creator_id"] = service.get()->get_creator_id(); + if (service.get()->get_buyer_id().has_value()) + service_json["buyer_id"] = service.get()->get_buyer_id().value(); + service_json["title"] = service.get()->get_title(); + service_json["description"] = service.get()->get_description(); + service_json["price"] = service.get()->get_price(); + service_json["status"] = service.get()->get_status(); + service_json["type"] = service.get()->get_type(); + if (service.get()->get_image_url().has_value()) + service_json["image_url"] = service.get()->get_image_url().value(); + service_json["created_at"] = service.get()->get_created_at(); + service_json["updated_at"] = service.get()->get_updated_at(); + + if (service.get()->get_creator().has_value()) { + crow::json::wvalue creator; + creator["id"] = service.get()->get_creator().value().get_id(); + creator["username"] = service.get()->get_creator().value().get_username(); + creator["type"] = service.get()->get_creator().value().get_type(); + creator["email"] = service.get()->get_creator().value().get_email(); + creator["balance"] = service.get()->get_creator().value().get_balance(); + creator["created_at"] = service.get()->get_creator().value().get_created_at(); + creator["updated_at"] = service.get()->get_creator().value().get_updated_at(); + + service_json["creator"] = crow::json::wvalue(creator); + } + + if (service.get()->get_buyer().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = service.get()->get_buyer().value().get_id(); + buyer["username"] = service.get()->get_buyer().value().get_username(); + buyer["type"] = service.get()->get_buyer().value().get_type(); + buyer["email"] = service.get()->get_buyer().value().get_email(); + buyer["balance"] = service.get()->get_buyer().value().get_balance(); + buyer["created_at"] = service.get()->get_buyer().value().get_created_at(); + buyer["updated_at"] = service.get()->get_buyer().value().get_updated_at(); + + service_json["buyer"] = crow::json::wvalue(buyer); + } + + crow::json::wvalue data{{"service", service_json}}; + + res.write(data.dump()); + res.code = 200; + res.end(); + + } catch (const std::exception &e) { + std::cerr << "Error getting service: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} + void ServiceController::get_services(pqxx::connection &db, const crow::request &req, crow::response &res) { try { crow::json::rvalue body = crow::json::load(req.body); @@ -75,19 +145,8 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["id"] = all_services[i].get()->get_id(); service["creator_id"] = all_services[i].get()->get_creator_id(); - if (all_services[i].get()->get_buyer_id().has_value()) { - crow::json::wvalue buyer; - buyer["id"] = all_services[i].get()->get_buyer().get_id(); - buyer["username"] = all_services[i].get()->get_buyer().get_username(); - buyer["type"] = all_services[i].get()->get_buyer().get_type(); - buyer["email"] = all_services[i].get()->get_buyer().get_email(); - buyer["balance"] = all_services[i].get()->get_buyer().get_balance(); - buyer["created_at"] = all_services[i].get()->get_buyer().get_created_at(); - buyer["updated_at"] = all_services[i].get()->get_buyer().get_updated_at(); - - service["buyer"] = crow::json::wvalue(buyer); + if (all_services[i].get()->get_buyer_id().has_value()) service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); - } service["title"] = all_services[i].get()->get_title(); service["description"] = all_services[i].get()->get_description(); service["price"] = all_services[i].get()->get_price(); @@ -98,16 +157,31 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & service["created_at"] = all_services[i].get()->get_created_at(); service["updated_at"] = all_services[i].get()->get_updated_at(); - crow::json::wvalue creator; - creator["id"] = all_services[i].get()->get_creator().get_id(); - creator["username"] = all_services[i].get()->get_creator().get_username(); - creator["type"] = all_services[i].get()->get_creator().get_type(); - creator["email"] = all_services[i].get()->get_creator().get_email(); - creator["balance"] = all_services[i].get()->get_creator().get_balance(); - creator["created_at"] = all_services[i].get()->get_creator().get_created_at(); - creator["updated_at"] = all_services[i].get()->get_creator().get_updated_at(); - - service["creator"] = crow::json::wvalue(creator); + if (all_services[i].get()->get_creator().has_value()) { + crow::json::wvalue creator; + creator["id"] = all_services[i].get()->get_creator().value().get_id(); + creator["username"] = all_services[i].get()->get_creator().value().get_username(); + creator["type"] = all_services[i].get()->get_creator().value().get_type(); + creator["email"] = all_services[i].get()->get_creator().value().get_email(); + creator["balance"] = all_services[i].get()->get_creator().value().get_balance(); + creator["created_at"] = all_services[i].get()->get_creator().value().get_created_at(); + creator["updated_at"] = all_services[i].get()->get_creator().value().get_updated_at(); + + service["creator"] = crow::json::wvalue(creator); + } + + if (all_services[i].get()->get_buyer().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = all_services[i].get()->get_buyer().value().get_id(); + buyer["username"] = all_services[i].get()->get_buyer().value().get_username(); + buyer["type"] = all_services[i].get()->get_buyer().value().get_type(); + buyer["email"] = all_services[i].get()->get_buyer().value().get_email(); + buyer["balance"] = all_services[i].get()->get_buyer().value().get_balance(); + buyer["created_at"] = all_services[i].get()->get_buyer().value().get_created_at(); + buyer["updated_at"] = all_services[i].get()->get_buyer().value().get_updated_at(); + + service["buyer"] = crow::json::wvalue(buyer); + } services.push_back(service); } diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 8b8f946..8e7fc19 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -153,4 +153,4 @@ void UserController::update_self(pqxx::connection &db, const crow::request &req, std::cerr << "Error updating user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); } -} \ No newline at end of file +} diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index a596ff9..9aa9bf1 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -1,8 +1,6 @@ #include -ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at) {} - -ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::string created_at, std::string updated_at, UserModel creator, UserModel buyer) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _created_at(created_at), _updated_at(updated_at), _creator(creator), _buyer(buyer) {} +ServiceModel::ServiceModel(std::string id, std::string creator_id, std::optional buyer_id, std::string title, std::string description, int price, std::string status, std::string type, std::optional image_url, std::optional creator, std::optional buyer, std::string created_at, std::string updated_at) : _id(id), _creator_id(creator_id), _buyer_id(buyer_id), _title(title), _description(description), _price(price), _status(status), _type(type), _image_url(image_url), _creator(creator), _buyer(buyer), _created_at(created_at), _updated_at(updated_at) {} std::string ServiceModel::get_id() const { return _id; } std::string ServiceModel::get_creator_id() const { return _creator_id; } @@ -13,10 +11,10 @@ int ServiceModel::get_price() const { return _price; } std::string ServiceModel::get_status() const { return _status; } std::string ServiceModel::get_type() const { return _type; } std::optional ServiceModel::get_image_url() const { return _image_url; } +std::optional ServiceModel::get_creator() const { return _creator; }; +std::optional ServiceModel::get_buyer() const { return _buyer; }; std::string ServiceModel::get_created_at() const { return _created_at; } std::string ServiceModel::get_updated_at() const { return _updated_at; } -UserModel ServiceModel::get_creator() const { return _creator; }; -UserModel ServiceModel::get_buyer() const { return _buyer; }; std::unique_ptr ServiceModel::create_service(pqxx::connection& db, const std::string& creator_id, const std::string& title, const std::string& description, const int price, const std::string& type, const std::optional& image_url, bool isThrow) { pqxx::work txn(db); @@ -53,9 +51,12 @@ std::unique_ptr ServiceModel::create_service(pqxx::connection& db, result[0]["status"].as(), result[0]["type"].as(), image_url_field, + std::nullopt, + std::nullopt, result[0]["created_at"].as(), result[0]["updated_at"].as()); } + std::vector> ServiceModel::get_services(pqxx::connection& db, const std::string& community_id, const std::string& status) { std::vector> all_services; @@ -156,10 +157,10 @@ std::vector> ServiceModel::get_services(pqxx::conn row["status"].as(), row["type"].as(), image_url_field, - row["created_at"].as(), - row["updated_at"].as(), creator, - buyer)); + buyer, + row["created_at"].as(), + row["updated_at"].as())); } return all_services; @@ -205,6 +206,8 @@ std::unique_ptr get_service(pqxx::connection& db, const std::strin result[0]["status"].as(), result[0]["type"].as(), image_url_field, + std::nullopt, + std::nullopt, result[0]["created_at"].as(), result[0]["updated_at"].as()); } @@ -217,7 +220,7 @@ std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connectio try { pqxx::work txn(db); - pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING id", id); + pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING *", id); txn.commit(); @@ -227,8 +230,32 @@ std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connectio else return nullptr; } - return std::make_unique(result[0]["id"].as()); + std::optional buyer_id_field; + std::optional image_url_field; + if (!result[0]["buyer_id"].is_null()) + buyer_id_field = result[0]["buyer_id"].as(); + else + buyer_id_field = std::nullopt; + if (!result[0]["image_url"].is_null()) + image_url_field = result[0]["image_url"].as(); + else + image_url_field = std::nullopt; + + return std::make_unique( + result[0]["id"].as(), + result[0]["creator_id"].as(), + buyer_id_field, + result[0]["title"].as(), + result[0]["description"].as(), + result[0]["price"].as(), + result[0]["status"].as(), + result[0]["type"].as(), + image_url_field, + std::nullopt, + std::nullopt, + result[0]["created_at"].as(), + result[0]["updated_at"].as()); } catch (const std::exception& e) { std::cerr << "Failed to delete service: " << e.what() << std::endl; return nullptr; diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 92da6ce..e093d0b 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -3,6 +3,10 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** GET /api/services + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { + ServiceController::get_service_by_id(db, req, res, service_id); + }); + CROW_ROUTE(app, "/api/services").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { ServiceController::get_services(db, req, res); }); diff --git a/test/tests/auth/register_test.cpp b/test/tests/auth/register_test.cpp index b19ec62..21b6c6a 100644 --- a/test/tests/auth/register_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -1,12 +1,10 @@ #include #include - #include // Para std::getenv #include #include #include #include - #include "../common.h" class RegisterValidations : public ::testing::Test { @@ -380,4 +378,4 @@ TEST_F(RegisterGeneralErrors, community_not_exists) { ASSERT_TRUE(json.contains("error")); std::string error_message_username = json["error"]; EXPECT_EQ(error_message_username, "community does not exists"); -} \ No newline at end of file +} diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index 9a676e3..e69de29 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -1,554 +0,0 @@ -#include -#include -#include - -#include // Para std::getenv -#include -#include -#include -#include - -#include "../common.h" -/* - -TEST if user not authentication with jwt -status_code = 401 - - */ - -TEST(DeleteServiceAuth, delete_service_not_auth) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/uuidexample"; - - auto response = cpr::Delete(cpr::Url{url}); - EXPECT_EQ(response.status_code, 404); - auto json = nlohmann::json::parse(response.text); - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "not token provided"); -} - -/* - -TEST if user is authenticated but not is admin and not creator -status_code = 401 -res.body = 'not enough priviligies'; - - // 1. crear un usuario admin - // 1.1 admin creado, crea un servicio y guardamos el id - // 2. obtener el codigo de la comunidad - // 3. crear un usuario neighbor - // 4. token neighbor - // 5. haces la peticion de eliminar el primer sericio - */ -class DeleteServiceNeitherAuth : public testing::Test { - protected: - std::string _service_id_; - std::string _neighbor_token_; - std::string _admin_token_; - std::string _admin_id_; - std::string _neighbor_id_; - std::string _community_id_; - - void register_admin() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "example@gmail.com"}, - {"username", "example"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin_token_ = token_value; - } else { - _admin_token_ = ""; - } - - auto json = nlohmann::json::parse(response.text); - _admin_id_ = json["id"]; - } - - void register_neighbor(std::string code) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - nlohmann::json new_user = { - {"email", "examplee@gmail.com"}, - {"username", "examplee"}, - {"password", "P@ssw0rd!"}, - {"type", "neighbor"}, - {"community_code", code}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _neighbor_token_ = token_value; - } else { - _neighbor_token_ = ""; - } - // auto json = nlohmann::json::parse(response.text); - _neighbor_id_ = json["id"]; - } - - void SetUp() override { - register_admin(); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; - nlohmann::json new_service = { - {"title", "nodeberiaexistir"}, - {"description", "some description 555555555"}, - {"price", 40}, - {"type", "offered"}}; - auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(s_create.text); - _service_id_ = json["id"]; - - sleep(1); - pqxx::connection conn(connection_string); - - if (conn.is_open()) { - try { - pqxx::work txn(conn); - - pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); - - txn.commit(); - - _community_id_ = result[0][0].as(); - } catch (const std::exception& e) { - std::cerr << "Error creating user: " << e.what() << std::endl; - } - } else { - std::cerr << "Error connecting to the database." << std::endl; - } - sleep(1); - register_neighbor(_community_id_); - } - - void TearDown() override { - clean_community_table(); - clean_user_table(); - // clean_service_table(); - } -}; - -TEST_F(DeleteServiceNeitherAuth, delete_service_Neither) { - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - - EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); -} - -/* - TEST if user is auth, exists 2 community (A y B), the user is admin of A, and services deleted is B community - so - status = 403 - res.body = user without admin privileges or not creator of service - */ -class DeleteServiceAdminBAuth : public testing::Test { - protected: - std::string _service_id_; - std::string _admin1_token_; - std::string _admin_token_; - std::string _admin_id_; - std::string _admin1_id_; - - void register_admin() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "example@gmail.com"}, - {"username", "example"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin_token_ = token_value; - } else { - _admin_token_ = ""; - } - - auto json = nlohmann::json::parse(response.text); - _admin_id_ = json["id"]; - } - - void register_admin1() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - nlohmann::json new_user = { - {"email", "examplee@gmail.com"}, - {"username", "examplee"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_namee"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin1_token_ = token_value; - } else { - _admin1_token_ = ""; - } - // auto json = nlohmann::json::parse(response.text); - _admin1_id_ = json["id"]; - } - - void SetUp() override { - register_admin(); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; - nlohmann::json new_service = { - {"title", "nodeberiaexistir"}, - {"description", "some description 555555555"}, - {"price", 40}, - {"type", "offered"}}; - auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(s_create.text); - _service_id_ = json["id"]; - sleep(1); - register_admin1(); - } - - void TearDown() override { - clean_community_table(); - clean_user_table(); - // clean_service_table(); - } -}; - -TEST_F(DeleteServiceAdminBAuth, delete_service_AdminB) { - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin1_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - - EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for user without admin privileges or not creator of service: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "user without admin privileges or not creator of service"); -} -/* - TEST if user is auth, not admin, but the user is creator of service - status_code = 204 - */ -class DeleteServiceCreatorAuth : public testing::Test { - protected: - std::string _service_id_; - std::string _neighbor_token_; - std::string _admin_token_; - std::string _admin_id_; - std::string _neighbor_id_; - std::string _community_id_; - - void register_admin() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "example@gmail.com"}, - {"username", "example"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin_token_ = token_value; - } else { - _admin_token_ = ""; - } - - auto json = nlohmann::json::parse(response.text); - _admin_id_ = json["id"]; - } - - void register_neighbor(std::string code) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - nlohmann::json new_user = { - {"email", "examplee@gmail.com"}, - {"username", "examplee"}, - {"password", "P@ssw0rd!"}, - {"type", "neighbor"}, - {"community_code", code}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _neighbor_token_ = token_value; - } else { - _neighbor_token_ = ""; - } - // auto json = nlohmann::json::parse(response.text); - _neighbor_id_ = json["id"]; - } - - void SetUp() override { - register_admin(); - - sleep(1); - pqxx::connection conn(connection_string); - - if (conn.is_open()) { - try { - pqxx::work txn(conn); - - pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); - - txn.commit(); - - _community_id_ = result[0][0].as(); - } catch (const std::exception& e) { - std::cerr << "Error creating user: " << e.what() << std::endl; - } - } else { - std::cerr << "Error connecting to the database." << std::endl; - } - sleep(1); - register_neighbor(_community_id_); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; - nlohmann::json new_service = { - {"title", "nodeberiaexistir"}, - {"description", "some description 555555555"}, - {"price", 40}, - {"type", "offered"}}; - auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(s_create.text); - _service_id_ = json["id"]; - } - - void TearDown() override { - clean_community_table(); - clean_user_table(); - // clean_service_table(); - } -}; - -TEST_F(DeleteServiceCreatorAuth, delete_service_creator) { - sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; - EXPECT_TRUE(json.contains("message")); - EXPECT_EQ(json["message"], "service deleted succesfully"); -} -/* - -TEST if user is admin and auth but service not found -status_code = 404 -res.body = 'service not found' - */ -class DeleteServiceNotFoundAuth : public testing::Test { - protected: - std::string _admin_token_; - - void register_admin() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "example@gmail.com"}, - {"username", "example"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin_token_ = token_value; - } else { - _admin_token_ = ""; - } - } - - void SetUp() override { - register_admin(); - } - - void TearDown() override { - clean_community_table(); - clean_user_table(); - // clean_service_table(); - } -}; - -TEST_F(DeleteServiceNotFoundAuth, delete_service_not_found) { - std::string lol = "123e4567-e89b-12d3-a456-426655440000"; - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + lol; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - - EXPECT_EQ(response.status_code, 404) << "Expected 404 status code for service not found: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "service not found"); -} -/* -TEST if user is admin and auth,and service exists -status_code = 204 -res.body = '' - */ -class DeleteServiceAdminAuth : public testing::Test { - protected: - std::string _service_id_; - std::string _neighbor_token_; - std::string _admin_token_; - std::string _admin_id_; - std::string _neighbor_id_; - std::string _community_id_; - - void register_admin() { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "example@gmail.com"}, - {"username", "example"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _admin_token_ = token_value; - } else { - _admin_token_ = ""; - } - - auto json = nlohmann::json::parse(response.text); - _admin_id_ = json["id"]; - } - - void register_neighbor(std::string code) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - nlohmann::json new_user = { - {"email", "examplee@gmail.com"}, - {"username", "examplee"}, - {"password", "P@ssw0rd!"}, - {"type", "neighbor"}, - {"community_code", code}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - _neighbor_token_ = token_value; - } else { - _neighbor_token_ = ""; - } - // auto json = nlohmann::json::parse(response.text); - _neighbor_id_ = json["id"]; - } - - void SetUp() override { - register_admin(); - - sleep(1); - pqxx::connection conn(connection_string); - - if (conn.is_open()) { - try { - pqxx::work txn(conn); - - pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", _admin_id_); - - txn.commit(); - - _community_id_ = result[0][0].as(); - } catch (const std::exception& e) { - std::cerr << "Error creating user: " << e.what() << std::endl; - } - } else { - std::cerr << "Error connecting to the database." << std::endl; - } - sleep(1); - register_neighbor(_community_id_); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; - nlohmann::json new_service = { - {"title", "nodeberiaexistir"}, - {"description", "some description 555555555"}, - {"price", 40}, - {"type", "offered"}}; - auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(s_create.text); - _service_id_ = json["id"]; - } - - void TearDown() override { - clean_community_table(); - clean_user_table(); - // clean_service_table(); - } -}; - -TEST_F(DeleteServiceAdminAuth, delete_service_admin) { - sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service deleted succesfully: "; - EXPECT_TRUE(json.contains("message")); - EXPECT_EQ(json["message"], "service deleted succesfully"); -} \ No newline at end of file diff --git a/test/tests/services/get_all_services_test.cpp b/test/tests/services/get_all_services_test.cpp index 93c29ea..60fc97e 100644 --- a/test/tests/services/get_all_services_test.cpp +++ b/test/tests/services/get_all_services_test.cpp @@ -1,12 +1,10 @@ #include #include - #include / #include #include #include #include - #include "../common.h" class GetServicesTest : public testing::Test { @@ -50,7 +48,7 @@ class GetServicesTest : public testing::Test { // * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas // ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: -TEST(GetServicesTest, first) { +TEST(GetServicesTest, get_all_services) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); From c3496173f3370f209c42cfb7c3564d228b88f585 Mon Sep 17 00:00:00 2001 From: Mampiz Date: Sat, 4 May 2024 01:02:31 +0200 Subject: [PATCH 32/47] task #148: delete_user_by_id final version + tests --- include/controllers/user_controller.h | 3 +- src/controllers/user_controller.cpp | 90 ++++- src/routes/user_routes.cpp | 6 +- test/tests/users/delete_user_test.cpp | 479 ++++++++++++++++++++++++++ 4 files changed, 564 insertions(+), 14 deletions(-) create mode 100644 test/tests/users/delete_user_test.cpp diff --git a/include/controllers/user_controller.h b/include/controllers/user_controller.h index 4483da1..d4b9a61 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -15,7 +15,8 @@ class UserController { public: static void get_users(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); - static void delete_user_by_id(pqxx::connection& db, crow::response& res, const std::string& user_id); + static void delete_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); static void update_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); static void update_self(pqxx::connection& db, const crow::request& req, crow::response& res); + static void delete_self(pqxx::connection &db, const crow::request &req, crow::response &res); }; diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 8f04a78..964ea7f 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -68,19 +68,54 @@ void UserController::get_user_by_id(pqxx::connection &db, const crow::request &r } } -void UserController::delete_user_by_id(pqxx::connection &db, crow::response &res, const std::string &user_id) { - try { - bool deleted = UserModel::delete_user_by_id(db, user_id); +void UserController::delete_user_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &user_id) +{ + try + { + crow::json::rvalue request = crow::json::load(req.body); + if (request["isAdmin"].b() == false) + { + handle_error(res, "not enough privileges", 403); + return; + } - if (deleted) { - res.code = 200; - crow::json::wvalue response_message; - response_message["message"] = "user deleted successfully"; - res.write(response_message.dump()); - res.end(); - } else - handle_error(res, "user not found", 404); - } catch (const std::exception &e) { + else if (!isValidUUID(user_id)) + { + handle_error(res, "invalid id", 400); + return; + } + + std::unique_ptr user = UserModel::get_user_by_id(db, user_id); + if (!user) { + handle_error(res, "user not found", 404); + return; + } + + std::string user_community = user.get()->get_community_id(); + + std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); + std::string admin_community = admin.get()->get_community_id(); + + if (user_community == admin_community) { + bool deleted = UserModel::delete_user_by_id(db, user_id); + if (deleted) + { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "user deleted successfully"; + res.write(response_message.dump()); + res.end(); + } + else + handle_error(res, "user not found", 404); + } + + else { + handle_error(res, "not enough privileges", 403); + } + } + catch (const std::exception &e) + { std::cerr << "Error deleting user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); } @@ -183,3 +218,34 @@ void UserController::update_self(pqxx::connection &db, const crow::request &req, handle_error(res, "internal server error", 500); } } + +void UserController::delete_self(pqxx::connection &db, const crow::request &req, crow::response &res) +{ + try + { + crow::json::rvalue request = crow::json::load(req.body); + std::string user_id = request["id"].s(); + if (request["isAdmin"].b() == true) + { + handle_error(res, "admin can't delete themselves", 403); + } + + bool deleted = UserModel::delete_user_by_id(db, user_id); + + if (deleted) + { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "user deleted successfully"; + res.write(response_message.dump()); + res.end(); + } + else + handle_error(res, "user not found", 404); + } + catch (const std::exception &e) + { + std::cerr << "Error deleting user: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} \ No newline at end of file diff --git a/src/routes/user_routes.cpp b/src/routes/user_routes.cpp index e91c144..3c7c340 100755 --- a/src/routes/user_routes.cpp +++ b/src/routes/user_routes.cpp @@ -10,7 +10,7 @@ void initialize_user_routes(NebyApp& app, pqxx::connection& db) { }); CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { - UserController::delete_user_by_id(db, res, user_id); + UserController::delete_user_by_id(db, req, res, user_id); }); CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { @@ -20,4 +20,8 @@ void initialize_user_routes(NebyApp& app, pqxx::connection& db) { CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { UserController::update_self(db, req, res); }); + + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + UserController::update_self(db, req, res); + }); } diff --git a/test/tests/users/delete_user_test.cpp b/test/tests/users/delete_user_test.cpp new file mode 100644 index 0000000..3b61501 --- /dev/null +++ b/test/tests/users/delete_user_test.cpp @@ -0,0 +1,479 @@ +#include +#include +#include +#include // Para std::getenv +#include +#include +#include +#include +#include "../common.h" + +/* +class deleteSelfNeighbor : public testing::Test +{ +protected: + std::string neighbor_token; + std::string admin_token; + std::string admin_id; + std::string neighbor_id; + std::string community_id; + + void register_admin() + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + admin_token = token_value; + } + else + { + admin_token = ""; + } + + auto json = nlohmann::json::parse(response.text); + admin_id = json["id"]; + } + + void register_neighbor(std::string code) + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + neighbor_token = token_value; + } + else + { + neighbor_token = ""; + } + // auto json = nlohmann::json::parse(response.text); + neighbor_id = json["id"]; + } + + void SetUp() override + { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) + { + try + { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); + + txn.commit(); + + community_id = result[0][0].as(); + } + catch (const std::exception &e) + { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } + else + { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(community_id); + } + + void TearDown() override + { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; +/* + +TEST_F(deleteSelfNeighbor, delete_self_bad_request) +{ + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; + nlohmann::json req_body = { + }; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Body{req_body.dump()}, cpr::Header{{"Content-Type", "application/json"}}, cpr::Cookies{{"token", neighbor_token}}); + EXPECT_EQ(response.status_code, 400); +} + + +TEST_F(deleteSelfNeighbor, delete_self_Neighbor_user) +{ + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200); + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "user deleted successfully"); +} + +TEST_F(deleteSelfNeighbor, delete_self_admin) +{ + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 403); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "admin can't delete themselves"); +} + +*/ +class DeleteUserById : public testing::Test +{ +protected: + std::string neighbor_token; + std::string admin_token; + std::string admin_id; + std::string neighbor_id; + std::string community_id; + + void register_admin() + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + admin_token = token_value; + } + else + { + admin_token = ""; + } + + auto json = nlohmann::json::parse(response.text); + admin_id = json["id"]; + } + + void register_neighbor(std::string code) + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + neighbor_token = token_value; + } + else + { + neighbor_token = ""; + } + // auto json = nlohmann::json::parse(response.text); + neighbor_id = json["id"]; + } + + void SetUp() override + { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) + { + try + { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); + + txn.commit(); + + community_id = result[0][0].as(); + } + catch (const std::exception &e) + { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } + else + { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(community_id); + } + + void TearDown() override + { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + + +TEST_F(DeleteUserById, delete_by_id_admin_succes) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user deleted successfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "user deleted successfully"); +} + +TEST_F(DeleteUserById, delete_by_id_neighbor_fail) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + admin_token; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for not enough privileges: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not enough privileges"); +} + + +TEST_F(DeleteUserById, delete_by_id_admin_invalid_id) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/6fe43f0-8b97"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid id"); +} + + +TEST_F(DeleteUserById, delete_by_id_admin_not_found) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/76fe43f0-8b97-4079-94e8-ef6f10d759b0"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 404) << "Expected 403 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user not found"); +} + + +class DeleteUserByIdDiffCommunity : public testing::Test +{ +protected: + std::string neighbor_token; + std::string admin_token; + std::string admin_token2; + std::string admin_id2; + std::string admin_id; + std::string neighbor_id; + std::string community_id; + + void register_admin() + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + admin_token = token_value; + } + else + { + admin_token = ""; + } + + auto json = nlohmann::json::parse(response.text); + admin_id = json["id"]; + } + + void register_neighbor(std::string code) + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + nlohmann::json new_user = { + {"email", "examplee@gmail.com"}, + {"username", "examplee"}, + {"password", "P@ssw0rd!"}, + {"type", "neighbor"}, + {"community_code", code}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + neighbor_token = token_value; + } + else + { + neighbor_token = ""; + + } + // auto json = nlohmann::json::parse(response.text); + neighbor_id = json["id"]; + } + + void register_admin2() + { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "exampleee@gmail.com"}, + {"username", "exampleee"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "second_example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) + { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + admin_token2 = token_value; + } + else + { + admin_token2 = ""; + } + + auto json = nlohmann::json::parse(response.text); + admin_id2 = json["id"]; + } + + void SetUp() override + { + register_admin(); + + sleep(1); + pqxx::connection conn(connection_string); + + if (conn.is_open()) + { + try + { + pqxx::work txn(conn); + + pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); + + txn.commit(); + + community_id = result[0][0].as(); + } + catch (const std::exception &e) + { + std::cerr << "Error creating user: " << e.what() << std::endl; + } + } + else + { + std::cerr << "Error connecting to the database." << std::endl; + } + sleep(1); + register_neighbor(community_id); + register_admin2(); + } + + void TearDown() override + { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteUserByIdDiffCommunity, delete_by_id_admin_diff_community) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id;; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token2}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "not enough privileges"); +} + From 9557ed6743a692a26b3e18ab94c1b51566ebcb0a Mon Sep 17 00:00:00 2001 From: leobelab Date: Mon, 6 May 2024 11:55:00 +0200 Subject: [PATCH 33/47] task #127: almost-finished-get-service-by-id --- src/controllers/service_controller.cpp | 7 +- test/tests/common.cpp | 19 ++++ test/tests/services/delete_service_test.cpp | 2 +- test/tests/services/get_service_test.cpp | 110 ++++++++++++++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 8cbcf93..54d0cb8 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -56,11 +56,16 @@ void ServiceController::get_service_by_id(pqxx::connection &db, const crow::requ handle_error(res, "id must be provided", 400); return; } + + if (!isValidUUID(service_id)) { + handle_error(res, "id is invalid", 400); + return; + } // Consulta el servicio por su ID std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); // Verifica si se encontró el servicio - if (!service) { + if (service == nullptr) { handle_error(res, "service not found", 404); return; } diff --git a/test/tests/common.cpp b/test/tests/common.cpp index 0ec0f31..85ca519 100644 --- a/test/tests/common.cpp +++ b/test/tests/common.cpp @@ -51,6 +51,25 @@ void clean_community_table() { } } +void clean_service_table() { + try { + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + pqxx::work txn(conn); + + txn.exec("DELETE FROM services"); + + txn.commit(); + + } else { + std::cout << "Error al conectar a la base de datos." << std::endl; + } + } catch (const std::exception& e) { + std::cout << "Error de excepción: " << e.what() << std::endl; + } +} + std::string create_user_test() { pqxx::connection conn(connection_string); diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index 3752cb3..c80e068 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -415,7 +415,7 @@ class DeleteServiceNotFoundAuth : public testing::Test { }; TEST_F(DeleteServiceNotFoundAuth, delete_service_not_found) { - std::string lol = "123e4567-e89b-12d3-a456-426655440000"; + std::string lol = "123e4567-e89b-12d3-a456-426655400123"; std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + lol; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index e69de29..477e7d2 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../common.h" + +class GetServiceTest : public testing::Test { + protected: + std::string _service_id_; + std::string _admin_id_; + std::string _admin_token_; + + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + void SetUp() override { + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service = { + {"title", "nodeberiaexistir"}, + {"description", "some description 555555555"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create.text); + _service_id_ = json["id"]; + sleep(1); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +// * void create_user_mock(nlohmann::json data); +//* void create_services_mock(std::string creator_id, int quantity); + +// * crear usuario y obtener su token, no crear servicios y solo existe una comunidad +// ? testear que devuelve un 200 y un servies array vacio + +// * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas +// ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: + +TEST_F(GetServiceTest, correct_id) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + "be2bb69d-f5f9-432b-b605-d8e939bf6ee2"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + std::cout << _service_id_ << std::endl; + auto json = nlohmann::json::parse(response.text); + + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service got succesfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "service got succesfully"); +} + +TEST_F(GetServiceTest, invalid_id) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/non_real_service_id"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 400) << "expect 400 status code"; + + auto json = nlohmann::json::parse(response.text); + + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid id service"); +} + +TEST_F(GetServiceTest, not_found_id) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/11111111-1111-1111-1111-111111111111"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 404) << "expect 404 status code"; + + auto json = nlohmann::json::parse(response.text); + + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "service not found"); +} From 8bac0c4b4cfcbda56bede25f3086ec4dacc9c1cb Mon Sep 17 00:00:00 2001 From: Mampiz Date: Mon, 6 May 2024 12:44:32 +0200 Subject: [PATCH 34/47] task #124: implementation of delete self and tests --- include/controllers/user_controller.h | 3 +- src/controllers/user_controller.cpp | 71 +++--- src/routes/user_routes.cpp | 16 +- test/tests/users/delete_user_test.cpp | 328 ++++++++++---------------- 4 files changed, 166 insertions(+), 252 deletions(-) diff --git a/include/controllers/user_controller.h b/include/controllers/user_controller.h index d4b9a61..f9567ff 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -5,7 +5,6 @@ #include #include #include - #include #include #include @@ -18,5 +17,5 @@ class UserController { static void delete_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); static void update_user_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& user_id); static void update_self(pqxx::connection& db, const crow::request& req, crow::response& res); - static void delete_self(pqxx::connection &db, const crow::request &req, crow::response &res); + static void delete_self(pqxx::connection& db, const crow::request& req, crow::response& res); }; diff --git a/src/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 964ea7f..e70f7ba 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -68,54 +68,46 @@ void UserController::get_user_by_id(pqxx::connection &db, const crow::request &r } } -void UserController::delete_user_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &user_id) -{ - try - { +void UserController::delete_user_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &user_id) { + try { crow::json::rvalue request = crow::json::load(req.body); - if (request["isAdmin"].b() == false) - { + if (request["isAdmin"].b() == false) { handle_error(res, "not enough privileges", 403); return; } - else if (!isValidUUID(user_id)) - { + else if (!isValidUUID(user_id)) { handle_error(res, "invalid id", 400); return; } std::unique_ptr user = UserModel::get_user_by_id(db, user_id); if (!user) { - handle_error(res, "user not found", 404); - return; + handle_error(res, "user not found", 404); + return; } std::string user_community = user.get()->get_community_id(); std::unique_ptr admin = UserModel::get_user_by_id(db, request["id"].s()); std::string admin_community = admin.get()->get_community_id(); - + if (user_community == admin_community) { - bool deleted = UserModel::delete_user_by_id(db, user_id); - if (deleted) - { - res.code = 200; - crow::json::wvalue response_message; - response_message["message"] = "user deleted successfully"; - res.write(response_message.dump()); - res.end(); - } - else - handle_error(res, "user not found", 404); - } + bool deleted = UserModel::delete_user_by_id(db, user_id); + if (deleted) { + res.code = 200; + crow::json::wvalue response_message; + response_message["message"] = "user deleted successfully"; + res.write(response_message.dump()); + res.end(); + } else + handle_error(res, "user not found", 404); + } else { - handle_error(res, "not enough privileges", 403); - } - } - catch (const std::exception &e) - { + handle_error(res, "not enough privileges", 403); + } + } catch (const std::exception &e) { std::cerr << "Error deleting user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); } @@ -219,33 +211,28 @@ void UserController::update_self(pqxx::connection &db, const crow::request &req, } } -void UserController::delete_self(pqxx::connection &db, const crow::request &req, crow::response &res) -{ - try - { +void UserController::delete_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { crow::json::rvalue request = crow::json::load(req.body); std::string user_id = request["id"].s(); - if (request["isAdmin"].b() == true) - { + + if (request["isAdmin"].b() == true) { handle_error(res, "admin can't delete themselves", 403); } bool deleted = UserModel::delete_user_by_id(db, user_id); - if (deleted) - { + if (deleted) { res.code = 200; crow::json::wvalue response_message; response_message["message"] = "user deleted successfully"; res.write(response_message.dump()); res.end(); - } - else + } else handle_error(res, "user not found", 404); - } - catch (const std::exception &e) - { + + } catch (const std::exception &e) { std::cerr << "Error deleting user: " << e.what() << std::endl; handle_error(res, "internal server error", 500); } -} \ No newline at end of file +} diff --git a/src/routes/user_routes.cpp b/src/routes/user_routes.cpp index 3c7c340..0cdd658 100755 --- a/src/routes/user_routes.cpp +++ b/src/routes/user_routes.cpp @@ -5,6 +5,14 @@ void initialize_user_routes(NebyApp& app, pqxx::connection& db) { UserController::get_users(db, req, res); }); + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + UserController::update_self(db, req, res); + }); + + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + UserController::delete_self(db, req, res); + }); + CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { UserController::get_user_by_id(db, req, res, user_id); }); @@ -16,12 +24,4 @@ void initialize_user_routes(NebyApp& app, pqxx::connection& db) { CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { UserController::update_user_by_id(db, req, res, user_id); }); - - CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - UserController::update_self(db, req, res); - }); - - CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - UserController::update_self(db, req, res); - }); } diff --git a/test/tests/users/delete_user_test.cpp b/test/tests/users/delete_user_test.cpp index 3b61501..1f74fc7 100644 --- a/test/tests/users/delete_user_test.cpp +++ b/test/tests/users/delete_user_test.cpp @@ -1,25 +1,22 @@ #include #include #include -#include // Para std::getenv +#include // Para std::getenv #include #include #include #include #include "../common.h" -/* -class deleteSelfNeighbor : public testing::Test -{ -protected: +class DeleteUserById : public testing::Test { + protected: std::string neighbor_token; std::string admin_token; std::string admin_id; std::string neighbor_id; std::string community_id; - void register_admin() - { + void register_admin() { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { @@ -34,16 +31,13 @@ class deleteSelfNeighbor : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); admin_token = token_value; - } - else - { + } else { admin_token = ""; } @@ -51,8 +45,7 @@ class deleteSelfNeighbor : public testing::Test admin_id = json["id"]; } - void register_neighbor(std::string code) - { + void register_neighbor(std::string code) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { {"email", "examplee@gmail.com"}, @@ -66,33 +59,27 @@ class deleteSelfNeighbor : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); neighbor_token = token_value; - } - else - { + } else { neighbor_token = ""; } // auto json = nlohmann::json::parse(response.text); neighbor_id = json["id"]; } - void SetUp() override - { + void SetUp() override { register_admin(); sleep(1); pqxx::connection conn(connection_string); - if (conn.is_open()) - { - try - { + if (conn.is_open()) { + try { pqxx::work txn(conn); pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); @@ -100,72 +87,78 @@ class deleteSelfNeighbor : public testing::Test txn.commit(); community_id = result[0][0].as(); - } - catch (const std::exception &e) - { + } catch (const std::exception &e) { std::cerr << "Error creating user: " << e.what() << std::endl; } - } - else - { + } else { std::cerr << "Error connecting to the database." << std::endl; } sleep(1); register_neighbor(community_id); } - void TearDown() override - { + void TearDown() override { clean_community_table(); clean_user_table(); // clean_service_table(); } }; -/* - -TEST_F(deleteSelfNeighbor, delete_self_bad_request) -{ - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; - nlohmann::json req_body = { - }; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Body{req_body.dump()}, cpr::Header{{"Content-Type", "application/json"}}, cpr::Cookies{{"token", neighbor_token}}); - EXPECT_EQ(response.status_code, 400); -} +TEST_F(DeleteUserById, delete_by_id_admin_succes) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id; -TEST_F(deleteSelfNeighbor, delete_self_Neighbor_user) -{ - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 200); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user deleted successfully: "; EXPECT_TRUE(json.contains("message")); EXPECT_EQ(json["message"], "user deleted successfully"); } -TEST_F(deleteSelfNeighbor, delete_self_admin) -{ - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; +TEST_F(DeleteUserById, delete_by_id_neighbor_fail) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + admin_token; + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 403); + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for not enough privileges: "; EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "admin can't delete themselves"); + EXPECT_EQ(json["error"], "not enough privileges"); +} + +TEST_F(DeleteUserById, delete_by_id_admin_invalid_id) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/6fe43f0-8b97"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "invalid id"); +} + +TEST_F(DeleteUserById, delete_by_id_admin_not_found) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/76fe43f0-8b97-4079-94e8-ef6f10d759b0"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 404) << "Expected 403 status code for invalid id: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "user not found"); } -*/ -class DeleteUserById : public testing::Test -{ -protected: +class DeleteUserByIdDiffCommunity : public testing::Test { + protected: std::string neighbor_token; std::string admin_token; + std::string admin_token2; + std::string admin_id2; std::string admin_id; std::string neighbor_id; std::string community_id; - void register_admin() - { + void register_admin() { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { @@ -180,16 +173,13 @@ class DeleteUserById : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); admin_token = token_value; - } - else - { + } else { admin_token = ""; } @@ -197,8 +187,7 @@ class DeleteUserById : public testing::Test admin_id = json["id"]; } - void register_neighbor(std::string code) - { + void register_neighbor(std::string code) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { {"email", "examplee@gmail.com"}, @@ -212,33 +201,56 @@ class DeleteUserById : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); neighbor_token = token_value; - } - else - { + } else { neighbor_token = ""; } // auto json = nlohmann::json::parse(response.text); neighbor_id = json["id"]; } - void SetUp() override - { + void register_admin2() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "exampleee@gmail.com"}, + {"username", "exampleee"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "second_example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + admin_token2 = token_value; + } else { + admin_token2 = ""; + } + + auto json = nlohmann::json::parse(response.text); + admin_id2 = json["id"]; + } + + void SetUp() override { register_admin(); sleep(1); pqxx::connection conn(connection_string); - if (conn.is_open()) - { - try - { + if (conn.is_open()) { + try { pqxx::work txn(conn); pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); @@ -246,89 +258,45 @@ class DeleteUserById : public testing::Test txn.commit(); community_id = result[0][0].as(); - } - catch (const std::exception &e) - { + } catch (const std::exception &e) { std::cerr << "Error creating user: " << e.what() << std::endl; } - } - else - { + } else { std::cerr << "Error connecting to the database." << std::endl; } sleep(1); register_neighbor(community_id); + register_admin2(); } - void TearDown() override - { + void TearDown() override { clean_community_table(); clean_user_table(); // clean_service_table(); } }; - -TEST_F(DeleteUserById, delete_by_id_admin_succes) { +TEST_F(DeleteUserByIdDiffCommunity, delete_by_id_admin_diff_community) { sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user deleted successfully: "; - EXPECT_TRUE(json.contains("message")); - EXPECT_EQ(json["message"], "user deleted successfully"); -} + ; -TEST_F(DeleteUserById, delete_by_id_neighbor_fail) { - sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + admin_token; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token2}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for not enough privileges: "; + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for invalid id: "; EXPECT_TRUE(json.contains("error")); EXPECT_EQ(json["error"], "not enough privileges"); } - -TEST_F(DeleteUserById, delete_by_id_admin_invalid_id) { - sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/6fe43f0-8b97"; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 400) << "Expected 400 status code for invalid id: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "invalid id"); -} - - -TEST_F(DeleteUserById, delete_by_id_admin_not_found) { - sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/76fe43f0-8b97-4079-94e8-ef6f10d759b0"; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); - auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 404) << "Expected 403 status code for invalid id: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "user not found"); -} - - -class DeleteUserByIdDiffCommunity : public testing::Test -{ -protected: +class DeleteUserSelf : public testing::Test { + protected: std::string neighbor_token; std::string admin_token; - std::string admin_token2; - std::string admin_id2; std::string admin_id; std::string neighbor_id; std::string community_id; - void register_admin() - { + void register_admin() { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { @@ -343,16 +311,13 @@ class DeleteUserByIdDiffCommunity : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); admin_token = token_value; - } - else - { + } else { admin_token = ""; } @@ -360,8 +325,7 @@ class DeleteUserByIdDiffCommunity : public testing::Test admin_id = json["id"]; } - void register_neighbor(std::string code) - { + void register_neighbor(std::string code) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; nlohmann::json new_user = { {"email", "examplee@gmail.com"}, @@ -375,67 +339,27 @@ class DeleteUserByIdDiffCommunity : public testing::Test std::string set_cookie_header = response.header["Set-Cookie"]; size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { + if (token_pos != std::string::npos) { size_t token_start = token_pos + 6; size_t token_end = set_cookie_header.find(";", token_start); std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); neighbor_token = token_value; - } - else - { + } else { neighbor_token = ""; - } // auto json = nlohmann::json::parse(response.text); neighbor_id = json["id"]; } - void register_admin2() - { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; - - nlohmann::json new_user = { - {"email", "exampleee@gmail.com"}, - {"username", "exampleee"}, - {"password", "P@ssw0rd!"}, - {"type", "admin"}, - {"community_name", "second_example_community_name"}}; - - auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); - - std::string set_cookie_header = response.header["Set-Cookie"]; - - size_t token_pos = set_cookie_header.find("token="); - if (token_pos != std::string::npos) - { - size_t token_start = token_pos + 6; - size_t token_end = set_cookie_header.find(";", token_start); - std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); - - admin_token2 = token_value; - } - else - { - admin_token2 = ""; - } - - auto json = nlohmann::json::parse(response.text); - admin_id2 = json["id"]; - } - - void SetUp() override - { + void SetUp() override { register_admin(); sleep(1); pqxx::connection conn(connection_string); - if (conn.is_open()) - { - try - { + if (conn.is_open()) { + try { pqxx::work txn(conn); pqxx::result result = txn.exec_params("SELECT communities.code FROM users JOIN communities ON users.community_id = communities.id WHERE users.id = $1", admin_id); @@ -443,37 +367,41 @@ class DeleteUserByIdDiffCommunity : public testing::Test txn.commit(); community_id = result[0][0].as(); - } - catch (const std::exception &e) - { + } catch (const std::exception &e) { std::cerr << "Error creating user: " << e.what() << std::endl; } - } - else - { + } else { std::cerr << "Error connecting to the database." << std::endl; } sleep(1); register_neighbor(community_id); - register_admin2(); } - void TearDown() override - { + void TearDown() override { clean_community_table(); clean_user_table(); // clean_service_table(); } }; -TEST_F(DeleteUserByIdDiffCommunity, delete_by_id_admin_diff_community) { +TEST_F(DeleteUserSelf, delete_self_succes) { sleep(1); - std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id;; - - auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token2}}, cpr::Header{{"Content-Type", "application/json"}}); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); - EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for invalid id: "; - EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "not enough privileges"); + EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for user deleted successfully: "; + EXPECT_TRUE(json.contains("message")); + EXPECT_EQ(json["message"], "user deleted successfully"); } +TEST_F(DeleteUserSelf, delete_self_admin) { + sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; + + auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(response.text); + EXPECT_EQ(response.status_code, 403) << "Expected 403 status code for admin can't delete themselves: "; + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "admin can't delete themselves"); +} From dc316db1b4ed3db34d04e5c6ebbdf52f2da98058 Mon Sep 17 00:00:00 2001 From: David Baque Date: Mon, 6 May 2024 14:24:44 +0200 Subject: [PATCH 35/47] set up --- include/controllers/notification_controller.h | 15 ++++++++++ include/models/notification_model.h | 12 ++++---- include/routes/notification_routes.h | 9 ++++++ src/controllers/notification_controller.cpp | 28 +++++++++++++++++++ src/main.cpp | 3 ++ src/models/notification_model.cpp | 8 ++++++ src/routes/notification_routes.cpp | 11 ++++++++ 7 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 include/controllers/notification_controller.h create mode 100644 include/routes/notification_routes.h create mode 100644 src/controllers/notification_controller.cpp create mode 100644 src/routes/notification_routes.cpp diff --git a/include/controllers/notification_controller.h b/include/controllers/notification_controller.h new file mode 100644 index 0000000..2afdeda --- /dev/null +++ b/include/controllers/notification_controller.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +class NotificationController { + public: + static void create_notification(pqxx::connection& db, const crow::request& req, crow::response& res); +}; diff --git a/include/models/notification_model.h b/include/models/notification_model.h index b9077c4..fffc9c6 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -6,18 +6,16 @@ class NotificationModel { private: std::string _id; std::string _sender_id; - std::string _receiver_id; std::string _service_id; std::string _status; std::string _created_at; std::string _updated_at; public: - NotificationModel(std::string id, std::string sender_id, std::string receiver_id, std::string service_id, std::string status); + NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status); - std::string get_id(); - std::string get_sender_id(); - std::string get_receiver_id(); - std::string get_service_id(); - std::string get_status(); + std::string get_id() const; + std::string get_sender_id() const; + std::string get_service_id() const; + std::string get_status() const; }; diff --git a/include/routes/notification_routes.h b/include/routes/notification_routes.h new file mode 100644 index 0000000..e3d46c8 --- /dev/null +++ b/include/routes/notification_routes.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include +#include +#include + +void initialize_notifications_routes(NebyApp& app, pqxx::connection& db); diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp new file mode 100644 index 0000000..b59887a --- /dev/null +++ b/src/controllers/notification_controller.cpp @@ -0,0 +1,28 @@ +#include + +void NotificationController::create_notification(pqxx::connection& db, const crow::request& req, crow::response& res) { + try { + //? get id that creates notification to get service + crow::json::rvalue body = crow::json::load(req.body); + std::string notifier_id = body["id"].s(); + //? substract the id of url param + //? check if service exist + //? check if service is in community of solicitant + //? create notification state PENDING + //? res.status_code = 201; + //? res.body = { "id", "sender_id", "service_id", "status", "created_at", "updated_at" } + //? update data.sql for support updated_at automatically + + crow::json::wvalue data({ + {"message", + "successfully"}, + }); + + res.code = 201; + res.write(data.dump()); + res.end(); + } catch (const std::exception& e) { + std::cerr << "Error in create_notification: " << e.what() << std::endl; + handle_error(res, "internal server errror", 500); + } +} diff --git a/src/main.cpp b/src/main.cpp index 41e6298..0271715 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -21,6 +22,7 @@ int main() { NebyApp app; std::string connection_string = std::format("dbname={} user={} password={} host={} port={}", DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT); pqxx::connection conn(connection_string); + if (conn.is_open()) { std::cout << "Opened database successfully: " << conn.dbname() << std::endl; @@ -32,6 +34,7 @@ int main() { initialize_auth_routes(app, conn); initialize_user_routes(app, conn); initialize_service_routes(app, conn); + initialize_notifications_routes(app, conn); app.port(HTTP_PORT).multithreaded().run(); conn.disconnect(); diff --git a/src/models/notification_model.cpp b/src/models/notification_model.cpp index e69de29..eefc663 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -0,0 +1,8 @@ +#include + +NotificationModel::NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status) : _id(id), _sender_id(sender_id), _service_id(service_id), _status(status) {} + +std::string NotificationModel::get_id() const { return _id; } +std::string NotificationModel::get_sender_id() const { return _sender_id; } +std::string NotificationModel::get_service_id() const { return _service_id; } +std::string NotificationModel::get_status() const { return _status; } diff --git a/src/routes/notification_routes.cpp b/src/routes/notification_routes.cpp new file mode 100644 index 0000000..bb6f10b --- /dev/null +++ b/src/routes/notification_routes.cpp @@ -0,0 +1,11 @@ +#include + +void initialize_notifications_routes(NebyApp& app, pqxx::connection& db) { + CROW_ROUTE(app, "/api/notifications/") + .methods(crow::HTTPMethod::POST) + //.CROW_MIDDLEWARES(app, VerifyJWT) + + ([&db](const crow::request& req, crow::response& res, const std::string& notification_id) { + NotificationController::create_notification(db, req, res); + }); +} From 6b728ce65e18e92b771c1f9e43ae672379a94de4 Mon Sep 17 00:00:00 2001 From: David Baque Date: Mon, 6 May 2024 18:38:07 +0200 Subject: [PATCH 36/47] update create_token for include all info of user --- include/controllers/auth_controller.h | 7 ++++++- include/controllers/notification_controller.h | 5 +++-- include/utils/auth.h | 4 +++- src/controllers/auth_controller.cpp | 10 ++------- src/controllers/notification_controller.cpp | 21 ++++++++++++------- src/routes/notification_routes.cpp | 4 ++-- src/utils/auth.cpp | 12 ++++++++--- 7 files changed, 38 insertions(+), 25 deletions(-) diff --git a/include/controllers/auth_controller.h b/include/controllers/auth_controller.h index d46f9b1..1f4f00a 100755 --- a/include/controllers/auth_controller.h +++ b/include/controllers/auth_controller.h @@ -3,13 +3,18 @@ #include #include #include +#include #include +#include #include +#include +#include // Include the ctime header for time functions #include +#include #include #include #include - + class AuthController { public: static void register_user(pqxx::connection& db, const crow::request& req, crow::response& res); diff --git a/include/controllers/notification_controller.h b/include/controllers/notification_controller.h index 2afdeda..b0515d0 100644 --- a/include/controllers/notification_controller.h +++ b/include/controllers/notification_controller.h @@ -1,9 +1,10 @@ #pragma once #include +#include +#include #include #include - #include #include #include @@ -11,5 +12,5 @@ class NotificationController { public: - static void create_notification(pqxx::connection& db, const crow::request& req, crow::response& res); + static void create_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id); }; diff --git a/include/utils/auth.h b/include/utils/auth.h index cf12b46..367671f 100755 --- a/include/utils/auth.h +++ b/include/utils/auth.h @@ -2,9 +2,11 @@ #include #include +#include +#include #include -std::string create_token(const std::string& userId, const std::string& type); +std::string create_token(std::unique_ptr& user); bool validate_token(const std::string& token); diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index 911c473..a8f0abc 100755 --- a/src/controllers/auth_controller.cpp +++ b/src/controllers/auth_controller.cpp @@ -1,10 +1,4 @@ #include -#include -#include -#include -#include // Include the ctime header for time functions -#include -#include void AuthController::register_user(pqxx::connection &db, const crow::request &req, crow::response &res) { try { @@ -53,7 +47,7 @@ void AuthController::register_user(pqxx::connection &db, const crow::request &re return; } - std::string jwtToken = create_token(user.get()->get_id(), type); + std::string jwtToken = create_token(user); int expirationTimeSeconds = 3600; time_t now = time(0); time_t expirationTime = now + expirationTimeSeconds; @@ -104,7 +98,7 @@ void AuthController::login_user(pqxx::connection &db, const crow::request &req, std::string password = body["password"].s(); if (BCrypt::validatePassword(password, encrypt_password)) { - std::string jwtToken = create_token(user.get()->get_id(), user.get()->get_type()); + std::string jwtToken = create_token(user); int expirationTimeSeconds = 3600; time_t now = time(0); diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index b59887a..7412cab 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -1,21 +1,26 @@ #include -void NotificationController::create_notification(pqxx::connection& db, const crow::request& req, crow::response& res) { +void NotificationController::create_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id) { try { - //? get id that creates notification to get service + //? get id that creates notification to get service crow::json::rvalue body = crow::json::load(req.body); - std::string notifier_id = body["id"].s(); - //? substract the id of url param - //? check if service exist - //? check if service is in community of solicitant + std::string notifier_id = body["id"].s(); + //? check if service exist + std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); + if (!service) { + handle_error(res, "service not found", 404); + return; + } + //? check if service is in community of solicitant + //? create notification state PENDING - //? res.status_code = 201; + //? res.status_code = 201; //? res.body = { "id", "sender_id", "service_id", "status", "created_at", "updated_at" } //? update data.sql for support updated_at automatically crow::json::wvalue data({ {"message", - "successfully"}, + service_id}, }); res.code = 201; diff --git a/src/routes/notification_routes.cpp b/src/routes/notification_routes.cpp index bb6f10b..7804fb2 100644 --- a/src/routes/notification_routes.cpp +++ b/src/routes/notification_routes.cpp @@ -5,7 +5,7 @@ void initialize_notifications_routes(NebyApp& app, pqxx::connection& db) { .methods(crow::HTTPMethod::POST) //.CROW_MIDDLEWARES(app, VerifyJWT) - ([&db](const crow::request& req, crow::response& res, const std::string& notification_id) { - NotificationController::create_notification(db, req, res); + ([&db](const crow::request& req, crow::response& res, const std::string& service_id) { + NotificationController::create_notification(db, req, res, service_id); }); } diff --git a/src/utils/auth.cpp b/src/utils/auth.cpp index 9b9172d..18150f6 100755 --- a/src/utils/auth.cpp +++ b/src/utils/auth.cpp @@ -2,12 +2,18 @@ std::string SECRET_JWT = std::string(std::getenv("SECRET_JWT")); -std::string create_token(const std::string& userId, const std::string& type) { +std::string create_token(std::unique_ptr& user) { auto token = jwt::create() .set_type("JWS") .set_issuer("auth0") - .set_payload_claim("id", jwt::claim(std::string(userId))) - .set_payload_claim("type", jwt::claim(std::string(type))) + .set_payload_claim("id", jwt::claim(std::string(user.get()->get_id()))) + .set_payload_claim("type", jwt::claim(std::string(user.get()->get_type()))) + .set_payload_claim("username", jwt::claim(std::string(user.get()->get_username()))) + .set_payload_claim("community_id", jwt::claim(std::string(user.get()->get_community_id()))) + .set_payload_claim("email", jwt::claim(std::string(user.get()->get_email()))) + .set_payload_claim("balance", jwt::claim(std::to_string(user.get()->get_balance()))) + .set_payload_claim("created_at", jwt::claim(std::string(user.get()->get_created_at()))) + .set_payload_claim("updated_at", jwt::claim(std::string(user.get()->get_updated_at()))) .sign(jwt::algorithm::hs256{SECRET_JWT}); return token; From aed553bf08697364a0315b21f8b4c3c8498880dc Mon Sep 17 00:00:00 2001 From: leobelab Date: Mon, 6 May 2024 21:07:20 +0200 Subject: [PATCH 37/47] task #127: get-service-by-id-finally-finished --- test/tests/services/get_service_test.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index 477e7d2..65c490a 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -72,15 +73,15 @@ class GetServiceTest : public testing::Test { // ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: TEST_F(GetServiceTest, correct_id) { - std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + "be2bb69d-f5f9-432b-b605-d8e939bf6ee2"; + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - std::cout << _service_id_ << std::endl; + std::cout << _service_id_ << " " << _admin_token_ << std::endl; auto json = nlohmann::json::parse(response.text); EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service got succesfully: "; - EXPECT_TRUE(json.contains("message")); - EXPECT_EQ(json["message"], "service got succesfully"); + ASSERT_TRUE(json.contains("service")); + ASSERT_TRUE(json["service"].is_object()) << "Expected the 'service' key to contain an object."; } TEST_F(GetServiceTest, invalid_id) { @@ -93,7 +94,7 @@ TEST_F(GetServiceTest, invalid_id) { auto json = nlohmann::json::parse(response.text); EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "invalid id service"); + EXPECT_EQ(json["error"], "id is invalid"); } TEST_F(GetServiceTest, not_found_id) { From 49e21bfac2bac1f2688b4a4be7dfeecee538d179 Mon Sep 17 00:00:00 2001 From: leobelab Date: Mon, 6 May 2024 21:16:49 +0200 Subject: [PATCH 38/47] task #127: get-service-by-id-finally-finished-and-completed --- test/tests/services/get_service_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index 65c490a..824e511 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -76,7 +76,6 @@ TEST_F(GetServiceTest, correct_id) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - std::cout << _service_id_ << " " << _admin_token_ << std::endl; auto json = nlohmann::json::parse(response.text); EXPECT_EQ(response.status_code, 200) << "Expected 200 status code for service got succesfully: "; From 9effbd58e377b5e00b17c3a55b1a4d6327070d5a Mon Sep 17 00:00:00 2001 From: David Baque Date: Mon, 6 May 2024 22:01:25 +0200 Subject: [PATCH 39/47] task #146: create notification service --- include/middlewares/verify_jwt.h | 47 +------------- include/models/notification_model.h | 11 +++- src/controllers/notification_controller.cpp | 47 ++++++++++++-- src/middlewares/verify_jwt.cpp | 68 +++++++++++++++++++++ src/models/notification_model.cpp | 53 +++++++++++++++- src/routes/notification_routes.cpp | 6 +- 6 files changed, 175 insertions(+), 57 deletions(-) diff --git a/include/middlewares/verify_jwt.h b/include/middlewares/verify_jwt.h index 8eea6bb..0d62efa 100755 --- a/include/middlewares/verify_jwt.h +++ b/include/middlewares/verify_jwt.h @@ -8,49 +8,6 @@ struct VerifyJWT : crow::ILocalMiddleware { struct context {}; - void before_handle(crow::request& req, crow::response& res, context& ctx) { - std::string token = get_token_cookie(req); - - if (token == "") { - handle_error(res, "not token provided", 404); - return; - } - - if (!validate_token(token)) { - handle_error(res, "invalid token", 401); - return; - } - - auto decoded = jwt::decode(token); - - std::string id; - std::string type; - - // Acceder al payload del token decodificado - for (auto& e : decoded.get_payload_json()) { - if (e.first == "id") { - id = e.second.get(); - } else if (e.first == "type") { - type = e.second.get(); - } - } - - if (req.body == "") { - crow::json::wvalue body; - - body["id"] = id; - body["isAdmin"] = (type == "admin"); - - req.body = body.dump(); - } else { - crow::json::wvalue body = crow::json::load(req.body); - - body["id"] = id; - body["isAdmin"] = (type == "admin"); - - req.body = body.dump(); - } - } - - void after_handle(crow::request& req, crow::response& res, context& ctx) {} + void before_handle(crow::request& req, crow::response& res, context& ctx); + void after_handle(crow::request& req, crow::response& res, context& ctx); }; diff --git a/include/models/notification_model.h b/include/models/notification_model.h index fffc9c6..3d7a7e5 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include class NotificationModel { private: @@ -12,10 +14,17 @@ class NotificationModel { std::string _updated_at; public: - NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status); + NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status, std::string created_at, std::string updated_at); std::string get_id() const; std::string get_sender_id() const; std::string get_service_id() const; std::string get_status() const; + std::string get_created_at() const; + std::string get_updated_at() const; + + static std::unique_ptr create_notification(pqxx::connection& db, const std::string& sender_id, const std::string& service_id, const std::string& status = NotificationStatus::PENDING, bool throw_when_null = false); + + // * if the requester has already requested the service before, it returns true, otherwise false + static bool is_requested(pqxx::connection& db, const std::string& sender_id); }; diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index 7412cab..76c1844 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -5,23 +5,58 @@ void NotificationController::create_notification(pqxx::connection& db, const cro //? get id that creates notification to get service crow::json::rvalue body = crow::json::load(req.body); std::string notifier_id = body["id"].s(); + int notifier_balance = body["request_balance"].i(); + //? check if service exist std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); if (!service) { handle_error(res, "service not found", 404); return; } + + //? check that the notifier has not previously requested the service + if (NotificationModel::is_requested(db, notifier_id)) { + handle_error(res, "you cannot request the service again", 400); + return; + } + + //? check if the notifier has enough money + if (notifier_balance < service.get()->get_price()) { + handle_error(res, "there is not enough balance", 400); + return; + } + //? check if service is in community of solicitant - + std::string notifier_community_id = body["request_community_id"].s(); + + std::string service_creator_id = service.get()->get_creator_id(); + std::unique_ptr creator = UserModel::get_user_by_id(db, service_creator_id); + std::string service_community = creator.get()->get_community_id(); + if (notifier_community_id != service_community) { + handle_error(res, "does not belong to your community", 400); + return; + } + + //? check if notifier is not the creator of service + if (notifier_id == service_creator_id) { + handle_error(res, "you cannot request your own service", 400); + return; + } + //? create notification state PENDING + std::unique_ptr new_notification = NotificationModel::create_notification(db, notifier_id, service_id, NotificationStatus::PENDING, true); + //? res.status_code = 201; //? res.body = { "id", "sender_id", "service_id", "status", "created_at", "updated_at" } - //? update data.sql for support updated_at automatically + //! update data.sql for support updated_at automatically - crow::json::wvalue data({ - {"message", - service_id}, - }); + crow::json::wvalue data; + data["id"] = new_notification.get()->get_id(); + data["sender_id"] = new_notification.get()->get_sender_id(); + data["service_id"] = new_notification.get()->get_service_id(); + data["status"] = new_notification.get()->get_status(); + data["created_at"] = new_notification.get()->get_created_at(); + data["updated_at"] = new_notification.get()->get_updated_at(); res.code = 201; res.write(data.dump()); diff --git a/src/middlewares/verify_jwt.cpp b/src/middlewares/verify_jwt.cpp index e69de29..946f4aa 100755 --- a/src/middlewares/verify_jwt.cpp +++ b/src/middlewares/verify_jwt.cpp @@ -0,0 +1,68 @@ +#include +void VerifyJWT::before_handle(crow::request& req, crow::response& res, context& ctx) { + std::string token = get_token_cookie(req); + + if (token == "") { + handle_error(res, "no token provided", 404); + return; + } + + if (!validate_token(token)) { + handle_error(res, "invalid token", 401); + return; + } + + auto decoded = jwt::decode(token); + + std::string id, type, community_id, username, email, created_at, updated_at, balance; + + for (auto& e : decoded.get_payload_json()) { + if (e.first == "id") { + id = e.second.get(); + } else if (e.first == "type") { + type = e.second.get(); + } else if (e.first == "community_id") { + community_id = e.second.get(); + } else if (e.first == "username") { + username = e.second.get(); + } else if (e.first == "email") { + email = e.second.get(); + } else if (e.first == "created_at") { + created_at = e.second.get(); + } else if (e.first == "updated_at") { + updated_at = e.second.get(); + } else if (e.first == "balance") { + balance = e.second.get(); + } + } + + if (req.body == "") { + crow::json::wvalue body; + + body["id"] = id; + body["isAdmin"] = (type == "admin"); + body["request_community_id"] = community_id; + body["request_username"] = username; + body["request_email"] = email; + body["request_created_at"] = created_at; + body["request_updated_at"] = updated_at; + body["request_balance"] = std::stoi(balance); + + req.body = body.dump(); + } else { + crow::json::wvalue body = crow::json::load(req.body); + + body["id"] = id; + body["isAdmin"] = (type == "admin"); + body["request_community_id"] = community_id; + body["request_username"] = username; + body["request_email"] = email; + body["request_created_at"] = created_at; + body["request_updated_at"] = updated_at; + body["request_balance"] = std::stoi(balance); + req.body = body.dump(); + } +} + +void VerifyJWT::after_handle(crow::request& req, crow::response& res, context& ctx) { +} diff --git a/src/models/notification_model.cpp b/src/models/notification_model.cpp index eefc663..f0f5d75 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -1,8 +1,59 @@ #include -NotificationModel::NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status) : _id(id), _sender_id(sender_id), _service_id(service_id), _status(status) {} +NotificationModel::NotificationModel(std::string id, std::string sender_id, std::string service_id, std::string status, std::string created_at, std::string updated_at) : _id(id), _sender_id(sender_id), _service_id(service_id), _status(status), _created_at(created_at), _updated_at(updated_at) {} std::string NotificationModel::get_id() const { return _id; } std::string NotificationModel::get_sender_id() const { return _sender_id; } std::string NotificationModel::get_service_id() const { return _service_id; } std::string NotificationModel::get_status() const { return _status; } +std::string NotificationModel::get_created_at() const { return _created_at; } +std::string NotificationModel::get_updated_at() const { return _updated_at; } + +std::unique_ptr NotificationModel::create_notification(pqxx::connection& db, const std::string& sender_id, const std::string& service_id, const std::string& status, bool throw_when_null) { + try { + pqxx::work txn(db); + + pqxx::result result = txn.exec_params("INSERT INTO notifications (sender_id, service_id, status) VALUES ($1, $2, $3) RETURNING id, sender_id, service_id, status, created_at, updated_at", sender_id, service_id, status); + + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw creation_exception("service could not be created"); + return nullptr; + } + + return std::make_unique( + result[0]["id"].as(), + result[0]["sender_id"].as(), + result[0]["service_id"].as(), + result[0]["status"].as(), + result[0]["created_at"].as(), + result[0]["updated_at"].as()); + } catch (const std::exception& e) { + std::cerr << "Error to create notification: " << e.what() << std::endl; + return nullptr; + } +} + +bool NotificationModel::is_requested(pqxx::connection& db, const std::string& sender_id) { + try { + pqxx::work txn(db); + + pqxx::result result = txn.exec_params("SELECT COUNT(*) FROM notifications WHERE sender_id = $1", sender_id); + + txn.commit(); + + if (!result.empty()) { + int count = result[0][0].as(); + + if (count > 0) return true; + } + + return false; + } catch (const std::exception& e) { + std::cerr << "Error to check notification sender_id exists yet: " << e.what() << std::endl; + + return false; + } +} diff --git a/src/routes/notification_routes.cpp b/src/routes/notification_routes.cpp index 7804fb2..b1c2364 100644 --- a/src/routes/notification_routes.cpp +++ b/src/routes/notification_routes.cpp @@ -3,9 +3,7 @@ void initialize_notifications_routes(NebyApp& app, pqxx::connection& db) { CROW_ROUTE(app, "/api/notifications/") .methods(crow::HTTPMethod::POST) - //.CROW_MIDDLEWARES(app, VerifyJWT) - - ([&db](const crow::request& req, crow::response& res, const std::string& service_id) { - NotificationController::create_notification(db, req, res, service_id); + .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { + NotificationController::create_notification(db, req, res, service_id); }); } From ae05004c490c6208a09b89b98cff38e81e316342 Mon Sep 17 00:00:00 2001 From: David Baque Date: Wed, 8 May 2024 14:32:53 +0200 Subject: [PATCH 40/47] task #138: refused notificaton done --- include/controllers/notification_controller.h | 1 + include/models/notification_model.h | 2 ++ src/controllers/notification_controller.cpp | 32 +++++++++++++++++++ src/models/notification_model.cpp | 28 ++++++++++++++++ src/models/service_model.cpp | 9 ------ src/routes/notification_routes.cpp | 6 ++++ 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/include/controllers/notification_controller.h b/include/controllers/notification_controller.h index b0515d0..ed87351 100644 --- a/include/controllers/notification_controller.h +++ b/include/controllers/notification_controller.h @@ -13,4 +13,5 @@ class NotificationController { public: static void create_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id); + static void handle_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& notification_id); }; diff --git a/include/models/notification_model.h b/include/models/notification_model.h index 3d7a7e5..97a7fe0 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -27,4 +27,6 @@ class NotificationModel { // * if the requester has already requested the service before, it returns true, otherwise false static bool is_requested(pqxx::connection& db, const std::string& sender_id); + + static std::unique_ptr handle_notification_status(pqxx::connection& db, const std::string& status, const std::string& notification_id, bool throw_when_null = false); }; diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index 76c1844..2b59d92 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -7,6 +7,9 @@ void NotificationController::create_notification(pqxx::connection& db, const cro std::string notifier_id = body["id"].s(); int notifier_balance = body["request_balance"].i(); + std::cout << "balancation " << notifier_balance << std::endl; + std::cout << "id notfiier " << notifier_id << std::endl; + //? check if service exist std::unique_ptr service = ServiceModel::get_service_by_id(db, service_id); if (!service) { @@ -66,3 +69,32 @@ void NotificationController::create_notification(pqxx::connection& db, const cro handle_error(res, "internal server errror", 500); } } + +void NotificationController::handle_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& notification_id) { + //? extarct query string param -> action = accepeted | refused + auto action = req.url_params.get("action"); + + //? check if action query param exists + if (!action) { + handle_error(res, "string query param (action) not provided", 400); + return; + } + + //? check if action = accepted || refused + if (!(std::string(action) == NotificationStatus::ACCEPTED || std::string(action) == NotificationStatus::REFUSED)) { + handle_error(res, "action not valid value", 400); + return; + } + + //? if action == accepted -> accept the notification and refused others + + if (action == NotificationStatus::REFUSED) { + std::unique_ptr notification_refued = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id); + } + + //? if action == refused -> refuse the notification + std::cout << action << std::endl; + res.code = 200; + res.body = "hola"; + res.end(); +} diff --git a/src/models/notification_model.cpp b/src/models/notification_model.cpp index f0f5d75..caed05c 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -57,3 +57,31 @@ bool NotificationModel::is_requested(pqxx::connection& db, const std::string& se return false; } } + +std::unique_ptr NotificationModel::handle_notification_status(pqxx::connection& db, const std::string& status, const std::string& notification_id, bool throw_when_null) { + try { + pqxx::work txn(db); + + pqxx::result result = txn.exec_params("UPDATE notifications SET status = $1 WHERE id = $2 RETURNING *", status, notification_id); + + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("service not found"); + else + return nullptr; + } + + return std::make_unique( + result[0]["id"].as(), + result[0]["sender_id"].as(), + result[0]["service_id"].as(), + result[0]["status"].as(), result[0]["created_at"].as(), + result[0]["updated_at"].as()); + + } catch (const std::exception& e) { + std::cerr << e.what() << '\n'; + return nullptr; + } +} diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index 5fe7b9d..f6e1e08 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -177,15 +177,6 @@ std::unique_ptr get_service(pqxx::connection& db, const std::strin else return nullptr; } - std::cout << "id: " << result[0]["id"].as() << std::endl; - std::cout << "creator_id: " << result[0]["creator_id"].as() << std::endl; - std::cout << "title: " << result[0]["title"].as() << std::endl; - std::cout << "description: " << result[0]["description"].as() << std::endl; - std::cout << "price: " << result[0]["price"].as() << std::endl; - std::cout << "status: " << result[0]["status"].as() << std::endl; - std::cout << "type: " << result[0]["type"].as() << std::endl; - std::cout << "created_at: " << result[0]["created_at"].as() << std::endl; - std::cout << "updated_at: " << result[0]["updated_at"].as() << std::endl; std::optional buyer_id_field; std::optional image_url_field; if (!result[0]["buyer_id"].is_null()) diff --git a/src/routes/notification_routes.cpp b/src/routes/notification_routes.cpp index b1c2364..9241b6b 100644 --- a/src/routes/notification_routes.cpp +++ b/src/routes/notification_routes.cpp @@ -6,4 +6,10 @@ void initialize_notifications_routes(NebyApp& app, pqxx::connection& db) { .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { NotificationController::create_notification(db, req, res, service_id); }); + + CROW_ROUTE(app, "/api/notifications/") + .methods(crow::HTTPMethod::PUT) + .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& notification_id) { + NotificationController::handle_notification(db, req, res, notification_id); + }); } From 1572b3de74bf7742d5324c75f058a96a27d4445b Mon Sep 17 00:00:00 2001 From: leobelab Date: Wed, 8 May 2024 15:20:23 +0200 Subject: [PATCH 41/47] task #152: endpoint created --- include/controllers/service_controller.h | 2 +- include/models/service_model.h | 2 + src/controllers/service_controller.cpp | 77 +++++++++++++ src/models/service_model.cpp | 109 ++++++++++++++++++ src/routes/service_routes.cpp | 4 + .../tests/services/get_services_self_test.cpp | 66 +++++++++++ 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 test/tests/services/get_services_self_test.cpp diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index 5f68e7b..954b934 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include @@ -26,6 +25,7 @@ class ServiceController { static void create_service(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_services(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_service_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id); + static void get_services_self(pqxx::connection& db, const crow::request& req, crow::response& res); static void delete_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); static void update_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); }; diff --git a/include/models/service_model.h b/include/models/service_model.h index 70d4061..4128ddf 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -47,6 +47,8 @@ class ServiceModel { static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); + static std::vector> get_services_self(pqxx::connection& db, const std::string& creator_id, const std::string& status = ""); + static std::unique_ptr delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null = false); static bool update_service_by_id(pqxx::connection& db, const std::string id, const std::string tittle, const std::string description, const int price); diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 54d0cb8..b969e77 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -202,6 +202,83 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & } } +void ServiceController::get_services_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { + crow::json::rvalue get = crow::json::load(req.body); + std::string user_id = get["id"].s(); + + std::vector> all_services; + + auto status = req.url_params.get("status"); + + if (!status) { + all_services = ServiceModel::get_services_self(db, user_id); + + } else if (status && (std::string(status) == ServiceStatus::CLOSED || std::string(status) == ServiceStatus::OPEN)) { + all_services = ServiceModel::get_services_self(db, user_id, status); + } else { + handle_error(res, "status not valid value", 400); + return; + } + + crow::json::wvalue::list services; + for (unsigned int i = 0; i < all_services.size(); ++i) { + crow::json::wvalue service; + + service["id"] = all_services[i].get()->get_id(); + service["creator_id"] = all_services[i].get()->get_creator_id(); + if (all_services[i].get()->get_buyer_id().has_value()) + service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); + service["title"] = all_services[i].get()->get_title(); + service["description"] = all_services[i].get()->get_description(); + service["price"] = all_services[i].get()->get_price(); + service["status"] = all_services[i].get()->get_status(); + service["type"] = all_services[i].get()->get_type(); + if (all_services[i].get()->get_image_url().has_value()) + service["image_url"] = all_services[i].get()->get_image_url().value(); + service["created_at"] = all_services[i].get()->get_created_at(); + service["updated_at"] = all_services[i].get()->get_updated_at(); + + if (all_services[i].get()->get_creator().has_value()) { + crow::json::wvalue creator; + creator["id"] = all_services[i].get()->get_creator().value().get_id(); + creator["username"] = all_services[i].get()->get_creator().value().get_username(); + creator["type"] = all_services[i].get()->get_creator().value().get_type(); + creator["email"] = all_services[i].get()->get_creator().value().get_email(); + creator["balance"] = all_services[i].get()->get_creator().value().get_balance(); + creator["created_at"] = all_services[i].get()->get_creator().value().get_created_at(); + creator["updated_at"] = all_services[i].get()->get_creator().value().get_updated_at(); + + service["creator"] = crow::json::wvalue(creator); + } + + if (all_services[i].get()->get_buyer().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = all_services[i].get()->get_buyer().value().get_id(); + buyer["username"] = all_services[i].get()->get_buyer().value().get_username(); + buyer["type"] = all_services[i].get()->get_buyer().value().get_type(); + buyer["email"] = all_services[i].get()->get_buyer().value().get_email(); + buyer["balance"] = all_services[i].get()->get_buyer().value().get_balance(); + buyer["created_at"] = all_services[i].get()->get_buyer().value().get_created_at(); + buyer["updated_at"] = all_services[i].get()->get_buyer().value().get_updated_at(); + + service["buyer"] = crow::json::wvalue(buyer); + } + + services.push_back(service); + } + + crow::json::wvalue data{{"self_services", services}}; + + res.write(data.dump()); + res.code = 200; + res.end(); + } catch (const std::exception &e) { + std::cerr << "Error getting user services: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} + void ServiceController::delete_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { try { crow::json::rvalue request = crow::json::load(req.body); diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index 5fe7b9d..48c290c 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -216,6 +216,115 @@ std::unique_ptr ServiceModel::get_service_by_id(pqxx::connection& return get_service(db, "id", id, throw_when_null); } +std::vector> ServiceModel::get_services_self(pqxx::connection& db, const std::string& creator_id, const std::string& status) { + std::vector> all_services; + + pqxx::work txn(db); + + std::string query = + "SELECT s.id AS service_id, " + "s.creator_id, " + "s.buyer_id, " + "s.title, " + "s.description, " + "s.price, " + "s.status, " + "s.type, " + "s.image_url, " + "s.created_at, " + "s.updated_at, " + "uc.id AS creator_id, " + "uc.community_id AS creator_community_id, " + "uc.username AS creator_username, " + "uc.email AS creator_email, " + "uc.type AS creator_type, " + "uc.balance AS creator_balance, " + "uc.created_at AS creator_created_at, " + "uc.updated_at AS creator_updated_at, " + "ub.id AS buyer_id, " + "ub.community_id AS buyer_community_id, " + "ub.username AS buyer_username, " + "ub.email AS buyer_email, " + "ub.type AS buyer_type, " + "ub.balance AS buyer_balance, " + "ub.created_at AS buyer_created_at, " + "ub.updated_at AS buyer_updated_at " + "FROM services AS s " + "JOIN users AS uc ON s.creator_id = uc.id " + "LEFT JOIN users AS ub ON s.buyer_id = ub.id " + "WHERE s.creator_id = $1"; + + // Agregar filtro de status si se proporciona + if (!status.empty()) { + query += " AND s.status = $2"; + } + + pqxx::result result; + if (!status.empty()) { + result = txn.exec_params(query, creator_id, status); + } else { + result = txn.exec_params(query, creator_id); + } + + txn.commit(); + + for (const auto& row : result) { + std::optional buyer_id_field; + std::optional image_url_field; + if (!row["buyer_id"].is_null()) + buyer_id_field = row["buyer_id"].as(); + else + buyer_id_field = std::nullopt; + if (!row["image_url"].is_null()) + image_url_field = row["image_url"].as(); + else + image_url_field = std::nullopt; + + // Crear instancia de UserModel para el creador + UserModel creator( + row["creator_id"].as(), + row["creator_community_id"].as(), + row["creator_username"].as(), + row["creator_email"].as(), + row["creator_type"].as(), + row["creator_balance"].as(), + row["creator_created_at"].as(), + row["creator_updated_at"].as()); + + // Crear instancia de UserModel para el comprador, si existe + UserModel buyer; + if (buyer_id_field) { + buyer = UserModel( + row["buyer_id"].as(), + row["buyer_community_id"].as(), + row["buyer_username"].as(), + row["buyer_email"].as(), + row["buyer_type"].as(), + row["buyer_balance"].as(), + row["buyer_created_at"].as(), + row["buyer_updated_at"].as()); + } + + // Crear instancia de ServiceModel con UserModel como argumento adicional + all_services.push_back(std::make_unique( + row["service_id"].as(), + row["creator_id"].as(), + buyer_id_field, + row["title"].as(), + row["description"].as(), + row["price"].as(), + row["status"].as(), + row["type"].as(), + image_url_field, + creator, + buyer, + row["created_at"].as(), + row["updated_at"].as())); + } + + return all_services; +} + std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null) { try { pqxx::work txn(db); diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 07c600c..5206400 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -11,6 +11,10 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { ServiceController::get_services(db, req, res); }); + CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + ServiceController::get_services_self(db, req, res); + }); + // ** GET /api/services/:id // ** POST /api/services diff --git a/test/tests/services/get_services_self_test.cpp b/test/tests/services/get_services_self_test.cpp new file mode 100644 index 0000000..6c64acb --- /dev/null +++ b/test/tests/services/get_services_self_test.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../common.h" + +class GetServicesSelfTest : public testing::Test { + protected: + std::string token1_; + std::string _user_id1; + std::string _user_id2; + nlohmann::json user1_ = { + {"email", "example@gmail.com"}, + {"username", "username"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + nlohmann::json user2_ = { + {"email", "example2@gmail.com"}, + {"username", "username2"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + void SetUp() override { + // create user1 + // create user 2 + // create services for user1 + // create services for user2 + } + + void TearDown() override { + // clear_user_table(); + // clear_community_table(); + // clear_services_table(); + } +}; + +// * void create_user_mock(nlohmann::json data); +//* void create_services_mock(std::string creator_id, int quantity); + +// * crear usuario y obtener su token, no crear servicios y solo existe una comunidad +// ? testear que devuelve un 200 y un servies array vacio + +// * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas +// ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: + +TEST(GetServicesSelfTest, get_all_services_self) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + + clean_user_table(); + clean_community_table(); +} From 0260985d022ed965e73b09594fed07c7a125073b Mon Sep 17 00:00:00 2001 From: leobelab Date: Wed, 8 May 2024 19:48:38 +0200 Subject: [PATCH 42/47] task #152: self view of created services done --- src/controllers/service_controller.cpp | 1 - src/routes/service_routes.cpp | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index b969e77..1d5eb30 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -133,7 +133,6 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & std::vector> all_services; auto status = req.url_params.get("status"); - if (!status) { all_services = ServiceModel::get_services(db, user.get()->get_community_id()); diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 5206400..c1a12c3 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -3,6 +3,9 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** GET /api/services + CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + ServiceController::get_services_self(db, req, res); + }); CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { ServiceController::get_service_by_id(db, req, res, service_id); }); @@ -11,10 +14,6 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { ServiceController::get_services(db, req, res); }); - CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - ServiceController::get_services_self(db, req, res); - }); - // ** GET /api/services/:id // ** POST /api/services From 95ec4d4e6f8cc1130395ed36bdbf944c09e94d09 Mon Sep 17 00:00:00 2001 From: David Baque Date: Wed, 8 May 2024 23:56:39 +0200 Subject: [PATCH 43/47] task #138: refactor middlweare --- include/db/connection_pool.h | 36 ++++++++++++ include/middlewares/verify_jwt.h | 5 ++ include/models/community_model.h | 2 +- include/models/notification_model.h | 2 + include/routes/auth_routes.h | 3 +- include/routes/notification_routes.h | 4 +- include/routes/service_routes.h | 3 +- include/routes/user_routes.h | 2 +- include/utils/common.h | 13 ++++- src/controllers/notification_controller.cpp | 65 ++++++++++++++------- src/db/conncection_pool.cpp | 46 +++++++++++++++ src/main.cpp | 27 ++------- src/middlewares/verify_jwt.cpp | 54 ++++++++--------- src/models/community_model.cpp | 14 ++++- src/models/notification_model.cpp | 25 ++++++++ src/routes/auth_routes.cpp | 24 +++++--- src/routes/notification_routes.cpp | 19 ++++-- src/routes/service_routes.cpp | 34 +++++++---- src/routes/user_routes.cpp | 40 ++++++++----- src/utils/auth.cpp | 7 --- src/utils/common.cpp | 8 +++ test/tests/auth/login_test.cpp | 8 +-- test/tests/services/delete_service_test.cpp | 20 +++---- test/tests/services/get_service_test.cpp | 1 - test/tests/services/update_service_test.cpp | 16 ++--- test/tests/users/delete_user_test.cpp | 28 ++++----- test/tests/users/update_user_admin_test.cpp | 15 ++--- 27 files changed, 342 insertions(+), 179 deletions(-) create mode 100644 include/db/connection_pool.h create mode 100644 src/db/conncection_pool.cpp diff --git a/include/db/connection_pool.h b/include/db/connection_pool.h new file mode 100644 index 0000000..2b0177c --- /dev/null +++ b/include/db/connection_pool.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +class ConnectionPool { + public: + static ConnectionPool& getInstance(const std::string& connectionString, int poolSize) { + static ConnectionPool instance(connectionString, poolSize); + return instance; + } + + ConnectionPool(const ConnectionPool&) = delete; + ConnectionPool& operator=(const ConnectionPool&) = delete; + + std::shared_ptr getConnection(); + + void releaseConnection(std::shared_ptr conn); + + private: + ConnectionPool(const std::string& connectionString, int poolSize); + + ~ConnectionPool() ; + + std::shared_ptr createConnection(); + + private: + std::string connectionString_; + int poolSize_; + std::deque> connections_; + std::mutex mutex_; + std::condition_variable condition_; +}; diff --git a/include/middlewares/verify_jwt.h b/include/middlewares/verify_jwt.h index 0d62efa..ce9141f 100755 --- a/include/middlewares/verify_jwt.h +++ b/include/middlewares/verify_jwt.h @@ -1,9 +1,12 @@ #pragma once #include +#include #include #include +#include #include +#include struct VerifyJWT : crow::ILocalMiddleware { struct context {}; @@ -11,3 +14,5 @@ struct VerifyJWT : crow::ILocalMiddleware { void before_handle(crow::request& req, crow::response& res, context& ctx); void after_handle(crow::request& req, crow::response& res, context& ctx); }; + +using NebyApp = crow::App; diff --git a/include/models/community_model.h b/include/models/community_model.h index bdeb1e9..57840d9 100755 --- a/include/models/community_model.h +++ b/include/models/community_model.h @@ -24,7 +24,7 @@ class CommunityModel { std::string get_created_at() const; std::string get_updated_at() const; - static std::string generate_community_code(); + static std::string generate_community_code(const std::string& seed); static std::unique_ptr create_community(pqxx::connection& db, const std::string& name, bool throw_when_null = false); diff --git a/include/models/notification_model.h b/include/models/notification_model.h index 97a7fe0..a45c758 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -29,4 +29,6 @@ class NotificationModel { static bool is_requested(pqxx::connection& db, const std::string& sender_id); static std::unique_ptr handle_notification_status(pqxx::connection& db, const std::string& status, const std::string& notification_id, bool throw_when_null = false); + + static bool refused_notifications(pqxx::connection& db, const std::string& service_id, const std::string& notification_id); }; diff --git a/include/routes/auth_routes.h b/include/routes/auth_routes.h index 32ea7b1..5cc7fac 100755 --- a/include/routes/auth_routes.h +++ b/include/routes/auth_routes.h @@ -5,5 +5,6 @@ #include #include #include +#include -void initialize_auth_routes(NebyApp& app, pqxx::connection& db); +void initialize_auth_routes(NebyApp& app); diff --git a/include/routes/notification_routes.h b/include/routes/notification_routes.h index e3d46c8..da8d2ea 100644 --- a/include/routes/notification_routes.h +++ b/include/routes/notification_routes.h @@ -2,8 +2,10 @@ #include #include +#include #include +#include #include #include -void initialize_notifications_routes(NebyApp& app, pqxx::connection& db); +void initialize_notifications_routes(NebyApp& app); diff --git a/include/routes/service_routes.h b/include/routes/service_routes.h index 93a873d..d732c08 100755 --- a/include/routes/service_routes.h +++ b/include/routes/service_routes.h @@ -3,7 +3,8 @@ #include #include #include +#include #include #include -void initialize_service_routes(NebyApp& app, pqxx::connection& db); +void initialize_service_routes(NebyApp& app); diff --git a/include/routes/user_routes.h b/include/routes/user_routes.h index 9544751..d632bcd 100755 --- a/include/routes/user_routes.h +++ b/include/routes/user_routes.h @@ -6,4 +6,4 @@ #include #include -void initialize_user_routes(NebyApp& app, pqxx::connection& db); +void initialize_user_routes(NebyApp& app); diff --git a/include/utils/common.h b/include/utils/common.h index faa8a5e..d556194 100755 --- a/include/utils/common.h +++ b/include/utils/common.h @@ -1,13 +1,22 @@ #pragma once #include +#include #include #include +#include #include #include -using NebyApp = crow::App; -struct Roles { +extern const std::string DB_NAME; +extern const std::string DB_USER; +extern const std::string DB_PASSWORD; +extern const std::string DB_HOST; +extern const int DB_PORT; +extern const int HTTP_PORT; +extern const std::string connection_string; + +struct Roles { static const std::string ADMIN; static const std::string NEIGHBOR; }; diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index 2b59d92..d03865e 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -71,30 +71,53 @@ void NotificationController::create_notification(pqxx::connection& db, const cro } void NotificationController::handle_notification(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& notification_id) { - //? extarct query string param -> action = accepeted | refused - auto action = req.url_params.get("action"); + try { + //? extarct query string param -> action = accepeted | refused + auto action = req.url_params.get("action"); - //? check if action query param exists - if (!action) { - handle_error(res, "string query param (action) not provided", 400); - return; - } + //? check if action query param exists + if (!action) { + handle_error(res, "string query param (action) not provided", 400); + return; + } - //? check if action = accepted || refused - if (!(std::string(action) == NotificationStatus::ACCEPTED || std::string(action) == NotificationStatus::REFUSED)) { - handle_error(res, "action not valid value", 400); - return; - } + //? check if action = accepted || refused + if (!(std::string(action) == NotificationStatus::ACCEPTED || std::string(action) == NotificationStatus::REFUSED)) { + handle_error(res, "action not valid value", 400); + return; + } - //? if action == accepted -> accept the notification and refused others + //? if action == accepted -> accept the notification and refused others - if (action == NotificationStatus::REFUSED) { - std::unique_ptr notification_refued = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id); - } + std::unique_ptr notification; + + if (action == NotificationStatus::REFUSED) { + notification = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id, true); + } else { + notification = NotificationModel::handle_notification_status(db, NotificationStatus::ACCEPTED, notification_id, true); - //? if action == refused -> refuse the notification - std::cout << action << std::endl; - res.code = 200; - res.body = "hola"; - res.end(); + bool succes_refused = NotificationModel::refused_notifications(db, notification.get()->get_service_id(), notification_id); + + if (!succes_refused) { + handle_error(res, "error in refused other notifications", 400); + return; + } + } + + //? if action == refused -> refuse the notification + std::cout << action << std::endl; + res.code = 200; + crow::json::wvalue data; + data["id"] = notification.get()->get_id(); + data["sender_id"] = notification.get()->get_sender_id(); + data["service_id"] = notification.get()->get_service_id(); + data["status"] = notification.get()->get_status(); + data["created_at"] = notification.get()->get_created_at(); + data["updated_at"] = notification.get()->get_updated_at(); + res.write(data.dump()); + + res.end(); + } catch (const std::exception& e) { + std::cerr << e.what() << '\n'; + } } diff --git a/src/db/conncection_pool.cpp b/src/db/conncection_pool.cpp new file mode 100644 index 0000000..fdb3971 --- /dev/null +++ b/src/db/conncection_pool.cpp @@ -0,0 +1,46 @@ +#include + +std::shared_ptr ConnectionPool::createConnection() { + auto conn = std::make_shared(connectionString_); + if (!conn->is_open()) { + std::cerr << "Error in connection: " << connectionString_ << std::endl; + exit(1); + } + std::cout << "Connection established. " << std::endl; + return conn; +} + +std::shared_ptr ConnectionPool::getConnection() { + std::unique_lock lock(mutex_); + + while (connections_.empty()) { + condition_.wait(lock); + } + + auto conn = connections_.front(); + connections_.pop_front(); + + return conn; +} + +void ConnectionPool::releaseConnection(std::shared_ptr conn) { + std::unique_lock lock(mutex_); + + connections_.push_back(conn); + + condition_.notify_one(); +} + +ConnectionPool::ConnectionPool(const std::string& connectionString, int poolSize) + : connectionString_(connectionString), poolSize_(poolSize) { + for (int i = 0; i < poolSize_; ++i) { + connections_.push_back(createConnection()); + } +} + +ConnectionPool::~ConnectionPool() { + for (auto& conn : connections_) { + conn->disconnect(); + } +} + \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0271715..6891bad 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -12,32 +13,14 @@ int main() { try { - int HTTP_PORT = std::stoi(std::getenv("HTTP_PORT")); - std::string DB_NAME = std::string(std::getenv("DB_NAME")); - std::string DB_USER = std::string(std::getenv("DB_USER")); - std::string DB_PASSWORD = std::string(std::getenv("DB_PASSWORD")); - std::string DB_HOST = std::string(std::getenv("DB_HOST")); - std::string DB_PORT = std::string(std::getenv("DB_PORT")); - NebyApp app; - std::string connection_string = std::format("dbname={} user={} password={} host={} port={}", DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT); - pqxx::connection conn(connection_string); - - - if (conn.is_open()) { - std::cout << "Opened database successfully: " << conn.dbname() << std::endl; - } else { - std::cout << "Can't open database" << std::endl; - exit(1); - } - initialize_auth_routes(app, conn); - initialize_user_routes(app, conn); - initialize_service_routes(app, conn); - initialize_notifications_routes(app, conn); + initialize_auth_routes(app); + initialize_user_routes(app); + initialize_service_routes(app); + initialize_notifications_routes(app); app.port(HTTP_PORT).multithreaded().run(); - conn.disconnect(); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; exit(1); diff --git a/src/middlewares/verify_jwt.cpp b/src/middlewares/verify_jwt.cpp index 946f4aa..2bff290 100755 --- a/src/middlewares/verify_jwt.cpp +++ b/src/middlewares/verify_jwt.cpp @@ -1,5 +1,8 @@ #include + void VerifyJWT::before_handle(crow::request& req, crow::response& res, context& ctx) { + ConnectionPool& pool = ConnectionPool::getInstance(connection_string, 10); + auto conn = pool.getConnection(); std::string token = get_token_cookie(req); if (token == "") { @@ -14,52 +17,41 @@ void VerifyJWT::before_handle(crow::request& req, crow::response& res, context& auto decoded = jwt::decode(token); - std::string id, type, community_id, username, email, created_at, updated_at, balance; + std::string id; for (auto& e : decoded.get_payload_json()) { - if (e.first == "id") { + if (e.first == "id") id = e.second.get(); - } else if (e.first == "type") { - type = e.second.get(); - } else if (e.first == "community_id") { - community_id = e.second.get(); - } else if (e.first == "username") { - username = e.second.get(); - } else if (e.first == "email") { - email = e.second.get(); - } else if (e.first == "created_at") { - created_at = e.second.get(); - } else if (e.first == "updated_at") { - updated_at = e.second.get(); - } else if (e.first == "balance") { - balance = e.second.get(); - } } + std::unique_ptr user = UserModel::get_user_by_id(*conn.get(), id); + + pool.releaseConnection(conn); + if (req.body == "") { crow::json::wvalue body; body["id"] = id; - body["isAdmin"] = (type == "admin"); - body["request_community_id"] = community_id; - body["request_username"] = username; - body["request_email"] = email; - body["request_created_at"] = created_at; - body["request_updated_at"] = updated_at; - body["request_balance"] = std::stoi(balance); + body["isAdmin"] = (user.get()->get_type() == "admin"); + body["request_community_id"] = user.get()->get_community_id(); + body["request_username"] = user.get()->get_username(); + body["request_email"] = user.get()->get_email(); + body["request_created_at"] = user.get()->get_created_at(); + body["request_updated_at"] = user.get()->get_updated_at(); + body["request_balance"] = user.get()->get_balance(); req.body = body.dump(); } else { crow::json::wvalue body = crow::json::load(req.body); body["id"] = id; - body["isAdmin"] = (type == "admin"); - body["request_community_id"] = community_id; - body["request_username"] = username; - body["request_email"] = email; - body["request_created_at"] = created_at; - body["request_updated_at"] = updated_at; - body["request_balance"] = std::stoi(balance); + body["isAdmin"] = (user.get()->get_type() == "admin"); + body["request_community_id"] = user.get()->get_community_id(); + body["request_username"] = user.get()->get_username(); + body["request_email"] = user.get()->get_email(); + body["request_created_at"] = user.get()->get_created_at(); + body["request_updated_at"] = user.get()->get_updated_at(); + body["request_balance"] = user.get()->get_balance(); req.body = body.dump(); } } diff --git a/src/models/community_model.cpp b/src/models/community_model.cpp index 05f59d7..4ee560f 100755 --- a/src/models/community_model.cpp +++ b/src/models/community_model.cpp @@ -8,12 +8,20 @@ std::string CommunityModel::get_code() const { return _code; } std::string CommunityModel::get_created_at() const { return _created_at; } std::string CommunityModel::get_updated_at() const { return _updated_at; } -std::string CommunityModel::generate_community_code() { +std::string CommunityModel::generate_community_code(const std::string& seed) { const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const int codeLength = 8; std::string code; - std::srand(std::time(nullptr)); + // Obtener la hora actual en segundos + std::time_t now = std::time(nullptr); + + // Convertir la semilla y la hora actual en una cadena para usar como base para la generación de código + std::string seedTime = seed + std::to_string(now); + + // Usar std::hash para obtener una semilla de generación única + std::size_t seedValue = std::hash{}(seedTime); + std::srand(seedValue); for (int i = 0; i < codeLength; ++i) { code += charset[std::rand() % charset.size()]; @@ -25,7 +33,7 @@ std::string CommunityModel::generate_community_code() { std::unique_ptr CommunityModel::create_community(pqxx::connection& db, const std::string& name, bool throw_when_null) { pqxx::work txn(db); - std::string code = generate_community_code(); + std::string code = generate_community_code(name); pqxx::result result = txn.exec_params("INSERT INTO communities (name, code) VALUES ($1, $2) RETURNING id, name, code, created_at, updated_at", name, code); diff --git a/src/models/notification_model.cpp b/src/models/notification_model.cpp index caed05c..28ee2e0 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -85,3 +85,28 @@ std::unique_ptr NotificationModel::handle_notification_status return nullptr; } } + +bool NotificationModel::refused_notifications(pqxx::connection& db, const std::string& service_id, const std::string& notification_id) { + try { + pqxx::work txn(db); + + // Ejecutar la consulta para actualizar las notificaciones + std::string sql = R"( + UPDATE notifications + SET status = 'refused', updated_at = CURRENT_TIMESTAMP + WHERE service_id = $1 + AND id != $2 + )"; + pqxx::result result = txn.exec_params(sql, service_id, notification_id); + + // Confirmar la transacción + txn.commit(); + + // Devolver true indicando que la operación fue exitosa + return true; + + } catch (const std::exception& e) { + std::cerr << "Error al actualizar las notificaciones: " << e.what() << '\n'; + return false; // Devolver false indicando que la operación falló + } +} diff --git a/src/routes/auth_routes.cpp b/src/routes/auth_routes.cpp index 263c70f..ee52f51 100755 --- a/src/routes/auth_routes.cpp +++ b/src/routes/auth_routes.cpp @@ -1,19 +1,29 @@ #include -void initialize_auth_routes(NebyApp& app, pqxx::connection& db) { +void initialize_auth_routes(NebyApp& app) { + + ConnectionPool& pool = ConnectionPool::getInstance(connection_string, 10); + CROW_ROUTE(app, "/api/auth/register") - .methods(crow::HTTPMethod::POST)([&db](const crow::request& req, crow::response& res) { - AuthController::register_user(db, req, res); + .methods(crow::HTTPMethod::POST)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + AuthController::register_user(*conn.get(), req, res); + pool.releaseConnection(conn); }); CROW_ROUTE(app, "/api/auth/login") - .methods(crow::HTTPMethod::POST)([&db](const crow::request& req, crow::response& res) { - AuthController::login_user(db, req, res); + .methods(crow::HTTPMethod::POST)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + std::cout << "me mamais los putisimos huevosss" << std::endl; + AuthController::login_user(*conn.get(), req, res); + pool.releaseConnection(conn); }); CROW_ROUTE(app, "/api/auth/self") .methods(crow::HTTPMethod::GET) - .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - AuthController::get_self(db, req, res); + .CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + AuthController::get_self(*conn.get(), req, res); + pool.releaseConnection(conn); }); } diff --git a/src/routes/notification_routes.cpp b/src/routes/notification_routes.cpp index 9241b6b..785f87a 100644 --- a/src/routes/notification_routes.cpp +++ b/src/routes/notification_routes.cpp @@ -1,15 +1,22 @@ #include -void initialize_notifications_routes(NebyApp& app, pqxx::connection& db) { +void initialize_notifications_routes(NebyApp& app) { + ConnectionPool& pool = ConnectionPool::getInstance(connection_string, 10); + CROW_ROUTE(app, "/api/notifications/") .methods(crow::HTTPMethod::POST) - .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { - NotificationController::create_notification(db, req, res, service_id); + .CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& service_id) { + auto conn = pool.getConnection(); + NotificationController::create_notification(*conn.get(), req, res, service_id); + pool.releaseConnection(conn); }); CROW_ROUTE(app, "/api/notifications/") .methods(crow::HTTPMethod::PUT) - .CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& notification_id) { - NotificationController::handle_notification(db, req, res, notification_id); - }); + .CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& notification_id) { + auto conn = pool.getConnection(); + NotificationController::handle_notification(*conn.get(), req, res, notification_id); + + pool.releaseConnection(conn); + }); } diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 07c600c..ed0d32c 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -1,33 +1,45 @@ #include -void initialize_service_routes(NebyApp& app, pqxx::connection& db) { +void initialize_service_routes(NebyApp& app) { // ** GET /api/services - CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { - ServiceController::get_service_by_id(db, req, res, service_id); + ConnectionPool& pool = ConnectionPool::getInstance(connection_string, 10); + + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& service_id) { + auto conn = pool.getConnection(); + ServiceController::get_service_by_id(*conn.get(), req, res, service_id); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/services").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - ServiceController::get_services(db, req, res); + CROW_ROUTE(app, "/api/services").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + ServiceController::get_services(*conn.get(), req, res); + pool.releaseConnection(conn); }); // ** GET /api/services/:id // ** POST /api/services - CROW_ROUTE(app, "/api/services").methods(crow::HTTPMethod::POST).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - ServiceController::create_service(db, req, res); + CROW_ROUTE(app, "/api/services").methods(crow::HTTPMethod::POST).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + ServiceController::create_service(*conn.get(), req, res); + pool.releaseConnection(conn); }); // ** PUT /api/services/:id - CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { - ServiceController::update_service(db, req, res, service_id); + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& service_id) { + auto conn = pool.getConnection(); + ServiceController::update_service(*conn.get(), req, res, service_id); + pool.releaseConnection(conn); }); // ** DELETE /api/services/:id - CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { - ServiceController::delete_service(db, req, res, service_id); + CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& service_id) { + auto conn = pool.getConnection(); + ServiceController::delete_service(*conn.get(), req, res, service_id); + pool.releaseConnection(conn); }); } diff --git a/src/routes/user_routes.cpp b/src/routes/user_routes.cpp index 0cdd658..be16628 100755 --- a/src/routes/user_routes.cpp +++ b/src/routes/user_routes.cpp @@ -1,27 +1,41 @@ #include -void initialize_user_routes(NebyApp& app, pqxx::connection& db) { - CROW_ROUTE(app, "/api/users").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - UserController::get_users(db, req, res); +void initialize_user_routes(NebyApp& app) { + ConnectionPool& pool = ConnectionPool::getInstance(connection_string, 10); + + CROW_ROUTE(app, "/api/users").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + UserController::get_users(*conn.get(), req, res); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - UserController::update_self(db, req, res); + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + UserController::update_self(*conn.get(), req, res); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - UserController::delete_self(db, req, res); + CROW_ROUTE(app, "/api/users/self").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + UserController::delete_self(*conn.get(), req, res); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { - UserController::get_user_by_id(db, req, res, user_id); + CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& user_id) { + auto conn = pool.getConnection(); + UserController::get_user_by_id(*conn.get(), req, res, user_id); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { - UserController::delete_user_by_id(db, req, res, user_id); + CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::DELETE).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& user_id) { + auto conn = pool.getConnection(); + UserController::delete_user_by_id(*conn.get(), req, res, user_id); + pool.releaseConnection(conn); }); - CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& user_id) { - UserController::update_user_by_id(db, req, res, user_id); + CROW_ROUTE(app, "/api/users/").methods(crow::HTTPMethod::PUT).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res, const std::string& user_id) { + auto conn = pool.getConnection(); + UserController::update_user_by_id(*conn.get(), req, res, user_id); + pool.releaseConnection(conn); }); } diff --git a/src/utils/auth.cpp b/src/utils/auth.cpp index 18150f6..3be33c5 100755 --- a/src/utils/auth.cpp +++ b/src/utils/auth.cpp @@ -7,13 +7,6 @@ std::string create_token(std::unique_ptr& user) { .set_type("JWS") .set_issuer("auth0") .set_payload_claim("id", jwt::claim(std::string(user.get()->get_id()))) - .set_payload_claim("type", jwt::claim(std::string(user.get()->get_type()))) - .set_payload_claim("username", jwt::claim(std::string(user.get()->get_username()))) - .set_payload_claim("community_id", jwt::claim(std::string(user.get()->get_community_id()))) - .set_payload_claim("email", jwt::claim(std::string(user.get()->get_email()))) - .set_payload_claim("balance", jwt::claim(std::to_string(user.get()->get_balance()))) - .set_payload_claim("created_at", jwt::claim(std::string(user.get()->get_created_at()))) - .set_payload_claim("updated_at", jwt::claim(std::string(user.get()->get_updated_at()))) .sign(jwt::algorithm::hs256{SECRET_JWT}); return token; diff --git a/src/utils/common.cpp b/src/utils/common.cpp index dbde653..3bf823e 100755 --- a/src/utils/common.cpp +++ b/src/utils/common.cpp @@ -1,5 +1,13 @@ #include +const int HTTP_PORT = std::stoi(std::getenv("HTTP_PORT")); +const std::string DB_NAME = std::string(std::getenv("DB_NAME")); +const std::string DB_USER = std::string(std::getenv("DB_USER")); +const std::string DB_PASSWORD = std::string(std::getenv("DB_PASSWORD")); +const std::string DB_HOST = std::string(std::getenv("DB_HOST")); +const int DB_PORT = std::stoi(std::getenv("DB_PORT")); +const std::string connection_string = std::format("dbname={} user={} password={} host={} port={}", DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT); + const std::string Roles::ADMIN = "admin"; const std::string Roles::NEIGHBOR = "neighbor"; diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp index 740f7a7..09120af 100644 --- a/test/tests/auth/login_test.cpp +++ b/test/tests/auth/login_test.cpp @@ -6,7 +6,7 @@ #include #include #include "../common.h" - + class LoginTest : public testing::Test { protected: std::string email_; @@ -21,18 +21,18 @@ class LoginTest : public testing::Test { {"email", email_}, {"username", "tupapiloko"}, {"password", password_}, - {"type", "admin"}, + {"type", "admin"}, {"community_name", "example_community_name"}}; auto r_register = cpr::Post(cpr::Url{url_register}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); } - + void TearDown() override { clean_user_table(); clean_community_table(); } }; - + class LoginErrorsTest : public testing::Test { protected: std::string email_; diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp index c80e068..29694bb 100644 --- a/test/tests/services/delete_service_test.cpp +++ b/test/tests/services/delete_service_test.cpp @@ -21,7 +21,7 @@ TEST(DeleteServiceAuth, delete_service_not_auth) { EXPECT_EQ(response.status_code, 404); auto json = nlohmann::json::parse(response.text); EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "not token provided"); + EXPECT_EQ(json["error"], "no token provided"); } /* @@ -108,7 +108,7 @@ class DeleteServiceNeitherAuth : public testing::Test { auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -126,7 +126,7 @@ class DeleteServiceNeitherAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); } @@ -228,7 +228,7 @@ class DeleteServiceAdminBAuth : public testing::Test { auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - sleep(1); + register_admin1(); } @@ -320,7 +320,7 @@ class DeleteServiceCreatorAuth : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -338,7 +338,7 @@ class DeleteServiceCreatorAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; nlohmann::json new_service = { @@ -359,7 +359,7 @@ class DeleteServiceCreatorAuth : public testing::Test { }; TEST_F(DeleteServiceCreatorAuth, delete_service_creator) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); @@ -497,7 +497,7 @@ class DeleteServiceAdminAuth : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -515,7 +515,7 @@ class DeleteServiceAdminAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; nlohmann::json new_service = { @@ -536,7 +536,7 @@ class DeleteServiceAdminAuth : public testing::Test { }; TEST_F(DeleteServiceAdminAuth, delete_service_admin) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); diff --git a/test/tests/services/get_service_test.cpp b/test/tests/services/get_service_test.cpp index 824e511..e233997 100644 --- a/test/tests/services/get_service_test.cpp +++ b/test/tests/services/get_service_test.cpp @@ -53,7 +53,6 @@ class GetServiceTest : public testing::Test { auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - sleep(1); } void TearDown() override { diff --git a/test/tests/services/update_service_test.cpp b/test/tests/services/update_service_test.cpp index 7653d47..3302b20 100644 --- a/test/tests/services/update_service_test.cpp +++ b/test/tests/services/update_service_test.cpp @@ -21,7 +21,7 @@ TEST(UpdateServiceNotAuth, update_service_not_auth) { EXPECT_EQ(response.status_code, 404); auto json = nlohmann::json::parse(response.text); EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "not token provided"); + EXPECT_EQ(json["error"], "no token provided"); } /* @@ -108,7 +108,6 @@ class UpdateServiceNeitherAuth : public testing::Test { auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - sleep(1); pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -126,7 +125,7 @@ class UpdateServiceNeitherAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); } @@ -228,7 +227,7 @@ class UpdateServiceAdminBAuth : public testing::Test { auto s_create = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service.dump()}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(s_create.text); _service_id_ = json["id"]; - sleep(1); + register_admin1(); } @@ -379,7 +378,6 @@ class UpdateServiceCreatorAuth : public testing::Test { void SetUp() override { register_admin(); - sleep(1); pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -397,7 +395,7 @@ class UpdateServiceCreatorAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; nlohmann::json new_service = { @@ -418,7 +416,6 @@ class UpdateServiceCreatorAuth : public testing::Test { }; TEST_F(UpdateServiceCreatorAuth, update_service_creator) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; nlohmann::json update = { {"price", 1}, @@ -436,7 +433,6 @@ status_code = 400 res.body = 'invalid price' */ TEST_F(UpdateServiceCreatorAuth, update_service_invalid_price) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; nlohmann::json update = { {"price", -11}}; @@ -520,7 +516,6 @@ class UpdateServiceAdminAuth : public testing::Test { void SetUp() override { register_admin(); - sleep(1); pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -538,7 +533,7 @@ class UpdateServiceAdminAuth : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; nlohmann::json new_service = { @@ -559,7 +554,6 @@ class UpdateServiceAdminAuth : public testing::Test { }; TEST_F(UpdateServiceAdminAuth, update_service_admin) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/" + _service_id_; nlohmann::json update = { {"price", 1}, diff --git a/test/tests/users/delete_user_test.cpp b/test/tests/users/delete_user_test.cpp index 1f74fc7..eec555f 100644 --- a/test/tests/users/delete_user_test.cpp +++ b/test/tests/users/delete_user_test.cpp @@ -23,7 +23,7 @@ class DeleteUserById : public testing::Test { {"email", "example@gmail.com"}, {"username", "example"}, {"password", "P@ssw0rd!"}, - {"type", "admin"}, + {"type", "admin"}, {"community_name", "example_community_name"}}; auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); @@ -75,7 +75,7 @@ class DeleteUserById : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -93,7 +93,7 @@ class DeleteUserById : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(community_id); } @@ -105,7 +105,7 @@ class DeleteUserById : public testing::Test { }; TEST_F(DeleteUserById, delete_by_id_admin_succes) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); @@ -116,7 +116,7 @@ TEST_F(DeleteUserById, delete_by_id_admin_succes) { } TEST_F(DeleteUserById, delete_by_id_neighbor_fail) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + admin_token; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); @@ -127,7 +127,7 @@ TEST_F(DeleteUserById, delete_by_id_neighbor_fail) { } TEST_F(DeleteUserById, delete_by_id_admin_invalid_id) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/6fe43f0-8b97"; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); @@ -138,7 +138,7 @@ TEST_F(DeleteUserById, delete_by_id_admin_invalid_id) { } TEST_F(DeleteUserById, delete_by_id_admin_not_found) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/76fe43f0-8b97-4079-94e8-ef6f10d759b0"; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); @@ -246,7 +246,7 @@ class DeleteUserByIdDiffCommunity : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -264,7 +264,7 @@ class DeleteUserByIdDiffCommunity : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(community_id); register_admin2(); } @@ -277,7 +277,7 @@ class DeleteUserByIdDiffCommunity : public testing::Test { }; TEST_F(DeleteUserByIdDiffCommunity, delete_by_id_admin_diff_community) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + neighbor_id; ; @@ -355,7 +355,7 @@ class DeleteUserSelf : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -373,7 +373,7 @@ class DeleteUserSelf : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(community_id); } @@ -385,7 +385,7 @@ class DeleteUserSelf : public testing::Test { }; TEST_F(DeleteUserSelf, delete_self_succes) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", neighbor_token}}, cpr::Header{{"Content-Type", "application/json"}}); @@ -396,7 +396,7 @@ TEST_F(DeleteUserSelf, delete_self_succes) { } TEST_F(DeleteUserSelf, delete_self_admin) { - sleep(1); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/self"; auto response = cpr::Delete(cpr::Url{url_service}, cpr::Cookies{{"token", admin_token}}, cpr::Header{{"Content-Type", "application/json"}}); diff --git a/test/tests/users/update_user_admin_test.cpp b/test/tests/users/update_user_admin_test.cpp index 5723ede..6c7b32f 100644 --- a/test/tests/users/update_user_admin_test.cpp +++ b/test/tests/users/update_user_admin_test.cpp @@ -21,7 +21,7 @@ TEST(UpdateAdminAuth, update_admin_not_auth) { EXPECT_EQ(response.status_code, 404); auto json = nlohmann::json::parse(response.text); EXPECT_TRUE(json.contains("error")); - EXPECT_EQ(json["error"], "not token provided"); + EXPECT_EQ(json["error"], "no token provided"); } /* @@ -97,8 +97,7 @@ class UpdateAdminNotAdmin : public testing::Test { void SetUp() override { register_admin(); - - sleep(1); + pqxx::connection conn(connection_string); if (conn.is_open()) { @@ -116,7 +115,7 @@ class UpdateAdminNotAdmin : public testing::Test { } else { std::cerr << "Error connecting to the database." << std::endl; } - sleep(1); + register_neighbor(_community_id_); } @@ -128,7 +127,6 @@ class UpdateAdminNotAdmin : public testing::Test { }; TEST_F(UpdateAdminNotAdmin, update_admin_not_admin) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _neighbor_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); @@ -187,7 +185,6 @@ class UpdateAdminInvalidId : public testing::Test { }; TEST_F(UpdateAdminInvalidId, update_admin_invalid_id) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + "123"; auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); @@ -246,7 +243,6 @@ class UpdateAdminNotFound : public testing::Test { }; TEST_F(UpdateAdminNotFound, update_admin_not_found) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + "d39f6c52-5de7-44b6-9ea4-87ccb1055097"; auto response = cpr::Put(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); auto json = nlohmann::json::parse(response.text); @@ -329,7 +325,7 @@ class UpdateAdminCommunityB : public testing::Test { void SetUp() override { register_admin(); - sleep(1); + register_admin1(); } @@ -394,7 +390,6 @@ class UpdateAdminInvalidUsername : public testing::Test { }; TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_username) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; nlohmann::json update = { {"username", "1"}}; @@ -406,7 +401,6 @@ TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_username) { } TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_balance) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; nlohmann::json update = { {"balance", "-1"}}; @@ -418,7 +412,6 @@ TEST_F(UpdateAdminInvalidUsername, update_admin_invalid_balance) { } TEST_F(UpdateAdminInvalidUsername, update_admin_succes) { - sleep(1); std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/users/" + _admin_id_; nlohmann::json update = { {"balance", "1"}, From f9974f63294d41497432cb8f626c29a264bc9fbf Mon Sep 17 00:00:00 2001 From: David Baque Date: Thu, 9 May 2024 12:31:21 +0200 Subject: [PATCH 44/47] task #138: manage service accpetion & refused --- include/models/notification_model.h | 2 + src/controllers/notification_controller.cpp | 38 +++++++++++---- src/models/notification_model.cpp | 53 +++++++++++++++------ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/include/models/notification_model.h b/include/models/notification_model.h index a45c758..2513dc1 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -31,4 +31,6 @@ class NotificationModel { static std::unique_ptr handle_notification_status(pqxx::connection& db, const std::string& status, const std::string& notification_id, bool throw_when_null = false); static bool refused_notifications(pqxx::connection& db, const std::string& service_id, const std::string& notification_id); + + static std::unique_ptr get_notification_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); }; diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index d03865e..9b87d0b 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -74,6 +74,8 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro try { //? extarct query string param -> action = accepeted | refused auto action = req.url_params.get("action"); + crow::json::rvalue body = crow::json::load(req.body); + std::string request_id = body["id"].s(); //? check if action query param exists if (!action) { @@ -87,16 +89,32 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro return; } + //? check if the notificaction exists + std::unique_ptr notification = NotificationModel::get_notification_by_id(db, notification_id); + + if (!notification) { + handle_error(res, "notification not found", 400); + return; + } + + //? check if the user making the request is the creator of the service + std::unique_ptr service = ServiceModel::get_service_by_id(db, notification.get()->get_service_id(), true); + + if (request_id != service.get()->get_creator_id()) { + handle_error(res, "you are not the creator of the service", 400); + return; + } + //? if action == accepted -> accept the notification and refused others - std::unique_ptr notification; + std::unique_ptr updated_notification; if (action == NotificationStatus::REFUSED) { - notification = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id, true); + updated_notification = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id, true); } else { - notification = NotificationModel::handle_notification_status(db, NotificationStatus::ACCEPTED, notification_id, true); + updated_notification = NotificationModel::handle_notification_status(db, NotificationStatus::ACCEPTED, notification_id, true); - bool succes_refused = NotificationModel::refused_notifications(db, notification.get()->get_service_id(), notification_id); + bool succes_refused = NotificationModel::refused_notifications(db, updated_notification.get()->get_service_id(), notification_id); if (!succes_refused) { handle_error(res, "error in refused other notifications", 400); @@ -108,12 +126,12 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro std::cout << action << std::endl; res.code = 200; crow::json::wvalue data; - data["id"] = notification.get()->get_id(); - data["sender_id"] = notification.get()->get_sender_id(); - data["service_id"] = notification.get()->get_service_id(); - data["status"] = notification.get()->get_status(); - data["created_at"] = notification.get()->get_created_at(); - data["updated_at"] = notification.get()->get_updated_at(); + data["id"] = updated_notification.get()->get_id(); + data["sender_id"] = updated_notification.get()->get_sender_id(); + data["service_id"] = updated_notification.get()->get_service_id(); + data["status"] = updated_notification.get()->get_status(); + data["created_at"] = updated_notification.get()->get_created_at(); + data["updated_at"] = updated_notification.get()->get_updated_at(); res.write(data.dump()); res.end(); diff --git a/src/models/notification_model.cpp b/src/models/notification_model.cpp index 28ee2e0..26ba721 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -77,7 +77,8 @@ std::unique_ptr NotificationModel::handle_notification_status result[0]["id"].as(), result[0]["sender_id"].as(), result[0]["service_id"].as(), - result[0]["status"].as(), result[0]["created_at"].as(), + result[0]["status"].as(), + result[0]["created_at"].as(), result[0]["updated_at"].as()); } catch (const std::exception& e) { @@ -87,26 +88,50 @@ std::unique_ptr NotificationModel::handle_notification_status } bool NotificationModel::refused_notifications(pqxx::connection& db, const std::string& service_id, const std::string& notification_id) { - try { - pqxx::work txn(db); + try { + pqxx::work txn(db); - // Ejecutar la consulta para actualizar las notificaciones - std::string sql = R"( + std::string sql = R"( UPDATE notifications SET status = 'refused', updated_at = CURRENT_TIMESTAMP WHERE service_id = $1 AND id != $2 + AND status = 'pending' )"; - pqxx::result result = txn.exec_params(sql, service_id, notification_id); - // Confirmar la transacción - txn.commit(); + pqxx::result result = txn.exec_params(sql, service_id, notification_id); - // Devolver true indicando que la operación fue exitosa - return true; + txn.commit(); + + return true; + + } catch (const std::exception& e) { + std::cerr << "Error al actualizar las notificaciones: " << e.what() << '\n'; + return false; + } +} + +std::unique_ptr get_notification(pqxx::connection& db, const std::string& column, const std::string& value, bool throw_when_null) { + pqxx::work txn(db); + pqxx::result result = txn.exec_params(std::format("SELECT * FROM notifications WHERE {} = $1", column), value); + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("notification not found"); + else + return nullptr; + } + + return std::make_unique( + result[0]["id"].as(), + result[0]["sender_id"].as(), + result[0]["service_id"].as(), + result[0]["status"].as(), + result[0]["created_at"].as(), + result[0]["updated_at"].as()); +} - } catch (const std::exception& e) { - std::cerr << "Error al actualizar las notificaciones: " << e.what() << '\n'; - return false; // Devolver false indicando que la operación falló - } +std::unique_ptr NotificationModel::get_notification_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null) { + return get_notification(db, "id", id, throw_when_null); } From 919054c46e983f47955b0494fb131fff809528cc Mon Sep 17 00:00:00 2001 From: voooigt Date: Sat, 11 May 2024 04:20:59 +0200 Subject: [PATCH 45/47] task#138 people can get paid now yaaay --- include/controllers/notification_controller.h | 1 + src/controllers/notification_controller.cpp | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/controllers/notification_controller.h b/include/controllers/notification_controller.h index ed87351..9cba36b 100644 --- a/include/controllers/notification_controller.h +++ b/include/controllers/notification_controller.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index 9b87d0b..0938b7d 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -112,6 +112,33 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro if (action == NotificationStatus::REFUSED) { updated_notification = NotificationModel::handle_notification_status(db, NotificationStatus::REFUSED, notification_id, true); } else { + std::unique_ptr notificationCreator = UserModel::get_user_by_id(db, notification->get_sender_id()); + std::unique_ptr serviceCreator = UserModel::get_user_by_id(db, service->get_creator_id()); + + if (service->get_type() == "offered") { + if (notificationCreator->get_balance() < service->get_price()) { + handle_error(res, "Notification sender does not have enough coins to pay for the service", 400); + return; + } + else { + int new_sender_balance = notificationCreator->get_balance() - service->get_price(); + int new_creator_balance = serviceCreator->get_balance() + service->get_price(); + UserModel::update_user_admin(db, notificationCreator->get_id(), notificationCreator->get_username(), new_sender_balance); + UserModel::update_user_admin(db, serviceCreator->get_id(), serviceCreator->get_username(), new_creator_balance); + } + } + else { + if (serviceCreator->get_balance() < service->get_price()) { + handle_error(res, "You don't have the coins to pay for this request", 400); + return; + } + else { + int new_sender_balance = notificationCreator->get_balance() + service->get_price(); + int new_creator_balance = serviceCreator->get_balance() - service->get_price(); + UserModel::update_user_admin(db, notificationCreator->get_id(), notificationCreator->get_username(), new_sender_balance); + UserModel::update_user_admin(db, serviceCreator->get_id(), serviceCreator->get_username(), new_creator_balance); + } + } updated_notification = NotificationModel::handle_notification_status(db, NotificationStatus::ACCEPTED, notification_id, true); bool succes_refused = NotificationModel::refused_notifications(db, updated_notification.get()->get_service_id(), notification_id); From ef93a594fdf9b0125c380c93acfef2ed0934496b Mon Sep 17 00:00:00 2001 From: voooigt Date: Sat, 11 May 2024 19:04:19 +0200 Subject: [PATCH 46/47] task#138 solved some format errors --- src/controllers/notification_controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp index 0938b7d..8dab42b 100644 --- a/src/controllers/notification_controller.cpp +++ b/src/controllers/notification_controller.cpp @@ -115,9 +115,9 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro std::unique_ptr notificationCreator = UserModel::get_user_by_id(db, notification->get_sender_id()); std::unique_ptr serviceCreator = UserModel::get_user_by_id(db, service->get_creator_id()); - if (service->get_type() == "offered") { + if (service->get_type() == ServiceType::OFFERED) { if (notificationCreator->get_balance() < service->get_price()) { - handle_error(res, "Notification sender does not have enough coins to pay for the service", 400); + handle_error(res, "notification sender does not have enough coins to pay for the service", 400); return; } else { @@ -129,7 +129,7 @@ void NotificationController::handle_notification(pqxx::connection& db, const cro } else { if (serviceCreator->get_balance() < service->get_price()) { - handle_error(res, "You don't have the coins to pay for this request", 400); + handle_error(res, "you don't have the coins to pay for this request", 400); return; } else { From 76d248d8d83de37c966655583558f9e0c27d75cc Mon Sep 17 00:00:00 2001 From: leobelab Date: Sun, 12 May 2024 20:22:52 +0200 Subject: [PATCH 47/47] task #152: get-service-self-tested --- .../tests/services/get_services_self_test.cpp | 190 +++++++++++++++--- 1 file changed, 164 insertions(+), 26 deletions(-) diff --git a/test/tests/services/get_services_self_test.cpp b/test/tests/services/get_services_self_test.cpp index 6c64acb..572c62a 100644 --- a/test/tests/services/get_services_self_test.cpp +++ b/test/tests/services/get_services_self_test.cpp @@ -9,33 +9,105 @@ class GetServicesSelfTest : public testing::Test { protected: - std::string token1_; - std::string _user_id1; - std::string _user_id2; - nlohmann::json user1_ = { - {"email", "example@gmail.com"}, - {"username", "username"}, - {"password", "Hola123."}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - nlohmann::json user2_ = { - {"email", "example2@gmail.com"}, - {"username", "username2"}, - {"password", "Hola123."}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; + std::string _service_id_; + std::string _admin_id_; + std::string _admin_id2_; + std::string _admin_token_; + std::string _admin_token2_; + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + void register_admin2() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example2@gmail.com"}, + {"username", "example2"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token2_ = token_value; + } else { + _admin_token2_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id2_ = json["id"]; + } void SetUp() override { - // create user1 - // create user 2 - // create services for user1 - // create services for user2 + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service1 = { + {"title", "nodeberiaexistir1"}, + {"description", "some description 1"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create1 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service1.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create1.text); + _service_id_ = json["id"]; + nlohmann::json new_service2 = { + {"title", "nodeberiaexistir2"}, + {"description", "some description 2"}, + {"price", 50}, + {"type", "offered"}}; + auto s_create2 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service2.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + nlohmann::json new_service3 = { + {"title", "nodeberiaexistir3"}, + {"description", "some description 3"}, + {"price", 60}, + {"type", "offered"}}; + auto s_create3 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service3.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + nlohmann::json new_service4 = { + {"title", "nodeberiaexistir4"}, + {"description", "some description 4"}, + {"price", 70}, + {"type", "offered"}}; + auto s_create4 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service4.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + sleep(1); + register_admin2(); } void TearDown() override { - // clear_user_table(); - // clear_community_table(); - // clear_services_table(); + clean_community_table(); + clean_user_table(); + // clean_service_table(); } }; @@ -48,10 +120,10 @@ class GetServicesSelfTest : public testing::Test { // * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas // ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: -TEST(GetServicesSelfTest, get_all_services_self) { +TEST_F(GetServicesSelfTest, get_all_services_self) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; - auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; @@ -60,7 +132,73 @@ TEST(GetServicesSelfTest, get_all_services_self) { ASSERT_TRUE(json.contains("self_services")); ASSERT_TRUE(json["self_services"].is_array()); + ASSERT_EQ(json["self_services"].size(), 4); +} + +TEST_F(GetServicesSelfTest, get_all_services_self_closed) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=closed"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "closed"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_open) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=open"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - clean_user_table(); - clean_community_table(); + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "open"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_wrong_status) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=NON_EXISTENT_STATUS"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 400) << "status not valid value"; + + auto json = nlohmann::json::parse(response.text); + + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "status not valid value"); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "closed"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_empty_list) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token2_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + ASSERT_TRUE(json["sel_services"].empty()); }