diff --git a/README.md b/README.md index 781894a..af5d0a8 100755 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # Backend + +[TEST](test/TEST_README.md) diff --git a/include/controllers/auth_controller.h b/include/controllers/auth_controller.h index 0270997..1f4f00a 100755 --- a/include/controllers/auth_controller.h +++ b/include/controllers/auth_controller.h @@ -3,15 +3,21 @@ #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); 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/include/controllers/notification_controller.h b/include/controllers/notification_controller.h new file mode 100644 index 0000000..9cba36b --- /dev/null +++ b/include/controllers/notification_controller.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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/controllers/service_controller.h b/include/controllers/service_controller.h index 7f83356..954b934 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -10,6 +10,7 @@ // ** custom includes #include #include +#include #include #include #include @@ -23,4 +24,8 @@ 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/controllers/user_controller.h b/include/controllers/user_controller.h index 0268f41..f9567ff 100755 --- a/include/controllers/user_controller.h +++ b/include/controllers/user_controller.h @@ -14,6 +14,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/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 29513a6..ce9141f 100755 --- a/include/middlewares/verify_jwt.h +++ b/include/middlewares/verify_jwt.h @@ -1,51 +1,18 @@ #pragma once #include +#include #include #include +#include #include +#include 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 (!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); }; + +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 b9077c4..2513dc1 100755 --- a/include/models/notification_model.h +++ b/include/models/notification_model.h @@ -1,23 +1,36 @@ #pragma once #include +#include +#include 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 created_at, std::string updated_at); - 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; + 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); + + 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/include/models/service_model.h b/include/models/service_model.h index 2741fc3..4128ddf 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -1,5 +1,6 @@ #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; @@ -19,14 +19,15 @@ 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; 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::optional creator, std::optional buyer, 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; @@ -37,8 +38,18 @@ class ServiceModel { std::optional get_image_url() const; std::string get_created_at() const; std::string get_updated_at() const; + std::optional get_creator() const; + std::optional get_buyer() 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); + 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::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/include/models/user_model.h b/include/models/user_model.h index 87fc6da..b879528 100755 --- a/include/models/user_model.h +++ b/include/models/user_model.h @@ -20,6 +20,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; @@ -45,4 +48,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/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 new file mode 100644 index 0000000..da8d2ea --- /dev/null +++ b/include/routes/notification_routes.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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/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/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/include/utils/validations.h b/include/utils/validations.h index 9a82d70..269c467 100755 --- a/include/utils/validations.h +++ b/include/utils/validations.h @@ -1,5 +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); diff --git a/src/controllers/auth_controller.cpp b/src/controllers/auth_controller.cpp index d4c3d39..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 { @@ -40,7 +34,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(); @@ -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; @@ -86,16 +80,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); @@ -114,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); @@ -133,17 +117,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; } @@ -152,3 +136,35 @@ void AuthController::login_user(pqxx::connection &db, const crow::request &req, handle_error(res, "INTERNAL SERVER ERROR", 500); } } + +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(); + 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/controllers/notification_controller.cpp b/src/controllers/notification_controller.cpp new file mode 100644 index 0000000..8dab42b --- /dev/null +++ b/src/controllers/notification_controller.cpp @@ -0,0 +1,168 @@ +#include + +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 + crow::json::rvalue body = crow::json::load(req.body); + 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) { + 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 + + 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()); + res.end(); + } catch (const std::exception& e) { + std::cerr << "Error in create_notification: " << e.what() << std::endl; + 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) { + 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) { + 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 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 updated_notification; + + 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() == 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); + 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); + + 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"] = 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(); + } catch (const std::exception& e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 08ddc59..1d5eb30 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()}, @@ -49,19 +50,104 @@ 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; + } + + 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 == nullptr) { + 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); 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) { 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(); @@ -75,6 +161,32 @@ 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(); + 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); } @@ -88,3 +200,183 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & handle_error(res, "internal server error", 500); } } + +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); + + 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()) { + 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, "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); + } +} + +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/controllers/user_controller.cpp b/src/controllers/user_controller.cpp index 75ddd43..e70f7ba 100755 --- a/src/controllers/user_controller.cpp +++ b/src/controllers/user_controller.cpp @@ -68,18 +68,45 @@ 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) { +void UserController::delete_user_by_id(pqxx::connection &db, const crow::request &req, crow::response &res, const std::string &user_id) { try { - bool deleted = UserModel::delete_user_by_id(db, user_id); + 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 + 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); @@ -88,36 +115,124 @@ 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; + + if (!isValidUUID(user_id)) { + handle_error(res, "invalid id", 400); + return; } - if (update.has("password")) { - temp_pass = update["password"].s(); - if (!validate_password(temp_pass, res)) 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, 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 = ""; + int temp_balance = -1; + if (update.has("username")) { + temp_name = update["username"].s(); + // 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("balance")) { + temp_balance = update["balance"].i(); + if (temp_balance < 0) { + handle_error(res, "invalid balance", 400); + return; + } + } + bool succes = UserModel::update_user_admin(db, user_id, temp_name, temp_balance); + 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, "not enough privileges", 403); } - 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); } } + +void UserController::update_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { + crow::json::rvalue update = crow::json::load(req.body); + std::string 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); + } +} + +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); + } +} 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 41e6298..6891bad 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include #include @@ -11,30 +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_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 e69de29..2bff290 100755 --- a/src/middlewares/verify_jwt.cpp +++ b/src/middlewares/verify_jwt.cpp @@ -0,0 +1,60 @@ +#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 == "") { + 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; + + for (auto& e : decoded.get_payload_json()) { + if (e.first == "id") + id = 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"] = (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"] = (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(); + } +} + +void VerifyJWT::after_handle(crow::request& req, crow::response& res, context& ctx) { +} 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 e69de29..26ba721 100755 --- a/src/models/notification_model.cpp +++ b/src/models/notification_model.cpp @@ -0,0 +1,137 @@ +#include + +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; + } +} + +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; + } +} + +bool NotificationModel::refused_notifications(pqxx::connection& db, const std::string& service_id, const std::string& notification_id) { + try { + pqxx::work txn(db); + + 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); + + 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()); +} + +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); +} diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index d52981c..9adc5e3 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::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_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; } @@ -12,13 +11,15 @@ 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; } -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 +43,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(), @@ -51,16 +51,61 @@ 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_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(); @@ -76,9 +121,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["community_id"].as(), + row["service_id"].as(), row["creator_id"].as(), buyer_id_field, row["title"].as(), @@ -87,9 +157,221 @@ std::vector> ServiceModel::get_open_services_by_co 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 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 services WHERE {} = $1", column), value); + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("service not found"); + else + return nullptr; + } + 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()); +} + +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); +} + +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); + + pqxx::result result = txn.exec_params("DELETE FROM services WHERE id = $1 RETURNING *", id); + + txn.commit(); + + if (result.empty()) { + if (throw_when_null) + throw data_not_found_exception("service not found"); + else + return nullptr; + } + + 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; + } +} + +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/models/user_model.cpp b/src/models/user_model.cpp index 3338405..7783336 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; } @@ -157,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/routes/auth_routes.cpp b/src/routes/auth_routes.cpp index 50e3506..ee52f51 100755 --- a/src/routes/auth_routes.cpp +++ b/src/routes/auth_routes.cpp @@ -1,13 +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)([&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 new file mode 100644 index 0000000..785f87a --- /dev/null +++ b/src/routes/notification_routes.cpp @@ -0,0 +1,22 @@ +#include + +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)([&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)([&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 80a6b55..80a03ff 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -1,21 +1,51 @@ #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) { - ServiceController::get_services(db, req, res); + 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)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + ServiceController::get_services(*conn.get(), req, res); + pool.releaseConnection(conn); + }); + + CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&pool](const crow::request& req, crow::response& res) { + auto conn = pool.getConnection(); + ServiceController::get_services_self(*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)([&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)([&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 4951853..be16628 100755 --- a/src/routes/user_routes.cpp +++ b/src/routes/user_routes.cpp @@ -1,19 +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)([&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)([&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, 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 9b9172d..3be33c5 100755 --- a/src/utils/auth.cpp +++ b/src/utils/auth.cpp @@ -2,12 +2,11 @@ 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()))) .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/src/utils/validations.cpp b/src/utils/validations.cpp index 8e83bdd..5144681 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); +} diff --git a/test/TEST_README.md b/test/TEST_README.md new file mode 100644 index 0000000..4b7ac69 --- /dev/null +++ b/test/TEST_README.md @@ -0,0 +1,93 @@ +# Integration Testing for C++ Server + +## Table of Contents +- [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 + +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)`: + - 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. + +## Tests Organization + +### Auth +- ["GET /api/auth/register"](tests/auth/lgoin.md) + +### Users + +### Services + +### Notifications diff --git a/test/tests/auth/login.md b/test/tests/auth/login.md new file mode 100644 index 0000000..e84c95e --- /dev/null +++ b/test/tests/auth/login.md @@ -0,0 +1,51 @@ +# 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. diff --git a/test/tests/auth/login_test.cpp b/test/tests/auth/login_test.cpp new file mode 100644 index 0000000..09120af --- /dev/null +++ b/test/tests/auth/login_test.cpp @@ -0,0 +1,193 @@ +#include +#include +#include // Para std::getenv +#include +#include +#include +#include +#include "../common.h" + +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", 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"]; + size_t token_pos = set_cookie_header.find("token="); + bool token_found = token_pos != std::string::npos; + + EXPECT_TRUE(token_found); +} + +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 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); +} + +TEST_F(LoginErrorsTest, 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(LoginValidationInputTest, incorrect_email) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + + // 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(LoginValidationInputTest, incorrect_password) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/login"; + + std::vector incorrect_passwords = { + "password", "12345678", "qwerty", "letmein", "abc123"}; + + for (const auto& password : incorrect_passwords) { + nlohmann::json post_data = { + {"email", "example@gmail.com"}, + {"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, 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 = { + {"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, 404); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "missing email field"); +} + +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"}, + }; + + 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, 404); + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "missing password field"); +} diff --git a/test/tests/auth_routes_test.cpp b/test/tests/auth/register_test.cpp old mode 100755 new mode 100644 similarity index 80% rename from test/tests/auth_routes_test.cpp rename to test/tests/auth/register_test.cpp index 8c32f87..21b6c6a --- a/test/tests/auth_routes_test.cpp +++ b/test/tests/auth/register_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include // Para std::getenv @@ -6,44 +5,27 @@ #include #include #include +#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 { - // Llamar a la función limpiarTablaUsers después de que se complete el test - limpiarTablaUsers(); + 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 = { @@ -57,7 +39,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 = { @@ -71,7 +53,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 = { @@ -85,7 +67,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 = { @@ -99,7 +81,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 = { @@ -114,7 +96,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 = { @@ -131,10 +113,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", @@ -147,7 +128,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"}, @@ -157,17 +137,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.", @@ -194,10 +173,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!", @@ -216,14 +194,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"}; @@ -241,10 +219,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 ", @@ -266,10 +243,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", @@ -288,11 +264,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"; @@ -311,14 +288,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 = { @@ -349,7 +320,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,11 +329,11 @@ 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); } -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 = { @@ -382,15 +353,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); } -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 = { @@ -408,5 +377,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 exists"); } diff --git a/test/tests/common.cpp b/test/tests/common.cpp new file mode 100644 index 0000000..85ca519 --- /dev/null +++ b/test/tests/common.cpp @@ -0,0 +1,156 @@ +#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.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; + } +} + +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); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + 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 + + 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 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 ""; +} + +void create_services() { + std::string user_id = create_user_test(); + + pqxx::connection conn(connection_string); + + if (conn.is_open()) { + try { + pqxx::work txn(conn); + + 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"; + + 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 { + return ""; + } +} diff --git a/test/tests/common.h b/test/tests/common.h old mode 100755 new mode 100644 index 1a00ca0..e0450a7 --- a/test/tests/common.h +++ b/test/tests/common.h @@ -1,6 +1,30 @@ -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 diff --git a/test/tests/services/create_service.md b/test/tests/services/create_service.md new file mode 100644 index 0000000..bc8f837 --- /dev/null +++ b/test/tests/services/create_service.md @@ -0,0 +1,30 @@ +# 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 new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/services/delete_service_test.cpp b/test/tests/services/delete_service_test.cpp new file mode 100644 index 0000000..29694bb --- /dev/null +++ b/test/tests/services/delete_service_test.cpp @@ -0,0 +1,546 @@ +#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"], "no token provided"); +} + +/* + +TEST if user is authenticated but not is admin and not creator +status_code = 403 +res.body = 'not enough priviligies'; + +*/ +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"]; + + + 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; + } + + 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"]; + + 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(); + + + 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; + } + + 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) { + + 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-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); + + 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(); + + + 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; + } + + 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) { + + 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"); +} 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..365f035 --- /dev/null +++ b/test/tests/services/get_all_services_test.cpp @@ -0,0 +1,66 @@ +#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, 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()}}); + + 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 new file mode 100644 index 0000000..e233997 --- /dev/null +++ b/test/tests/services/get_service_test.cpp @@ -0,0 +1,109 @@ +#include +#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"]; + } + + 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/" + _service_id_; + + auto response = cpr::Get(cpr::Url{url}, 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 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) { + 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"], "id is invalid"); +} + +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"); +} diff --git a/test/tests/services/get_services.md b/test/tests/services/get_services.md new file mode 100644 index 0000000..55e49d2 --- /dev/null +++ b/test/tests/services/get_services.md @@ -0,0 +1,115 @@ +# 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. + + +```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 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..572c62a --- /dev/null +++ b/test/tests/services/get_services_self_test.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../common.h" + +class GetServicesSelfTest : public testing::Test { + protected: + 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 { + 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 { + 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(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", _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()); + 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"}}); + + 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()); +} diff --git a/test/tests/services/update_service_test.cpp b/test/tests/services/update_service_test.cpp new file mode 100644 index 0000000..3302b20 --- /dev/null +++ b/test/tests/services/update_service_test.cpp @@ -0,0 +1,566 @@ +#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"], "no 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"]; + + 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; + } + + 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"]; + + 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(); + + 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; + } + + 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) { + 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) { + 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(); + + 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; + } + + 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) { + 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"); +} diff --git a/test/tests/test_one.cpp b/test/tests/test_one.cpp old mode 100755 new mode 100644 diff --git a/test/tests/users/delete_user_test.cpp b/test/tests/users/delete_user_test.cpp new file mode 100644 index 0000000..eec555f --- /dev/null +++ b/test/tests/users/delete_user_test.cpp @@ -0,0 +1,407 @@ +#include +#include +#include +#include // Para std::getenv +#include +#include +#include +#include +#include "../common.h" + +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(); + + + 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; + } + + register_neighbor(community_id); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteUserById, delete_by_id_admin_succes) { + + 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) { + + 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) { + + 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) { + + 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(); + + + 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; + } + + 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) { + + 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"); +} + +class DeleteUserSelf : 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(); + + + 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; + } + + register_neighbor(community_id); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(DeleteUserSelf, delete_self_succes) { + + 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, 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) { + + 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"); +} 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..6c7b32f --- /dev/null +++ b/test/tests/users/update_user_admin_test.cpp @@ -0,0 +1,424 @@ +#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"], "no 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(); + + 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; + } + + register_neighbor(_community_id_); + } + + void TearDown() override { + clean_community_table(); + clean_user_table(); + // clean_service_table(); + } +}; + +TEST_F(UpdateAdminNotAdmin, update_admin_not_admin) { + 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) { + 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) { + 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(); + + 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) { + 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) { + 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) { + 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"); +}