From 1572b3de74bf7742d5324c75f058a96a27d4445b Mon Sep 17 00:00:00 2001 From: leobelab Date: Wed, 8 May 2024 15:20:23 +0200 Subject: [PATCH 1/3] task #152: endpoint created --- include/controllers/service_controller.h | 2 +- include/models/service_model.h | 2 + src/controllers/service_controller.cpp | 77 +++++++++++++ src/models/service_model.cpp | 109 ++++++++++++++++++ src/routes/service_routes.cpp | 4 + .../tests/services/get_services_self_test.cpp | 66 +++++++++++ 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 test/tests/services/get_services_self_test.cpp diff --git a/include/controllers/service_controller.h b/include/controllers/service_controller.h index 5f68e7b..954b934 100755 --- a/include/controllers/service_controller.h +++ b/include/controllers/service_controller.h @@ -1,7 +1,6 @@ #pragma once #include - #include #include #include @@ -26,6 +25,7 @@ class ServiceController { static void create_service(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_services(pqxx::connection& db, const crow::request& req, crow::response& res); static void get_service_by_id(pqxx::connection& db, const crow::request& req, crow::response& res, const std::string& service_id); + static void get_services_self(pqxx::connection& db, const crow::request& req, crow::response& res); static void delete_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); static void update_service(pqxx::connection& db, const crow::request& req, crow::response& res, std::string service_id); }; diff --git a/include/models/service_model.h b/include/models/service_model.h index 70d4061..4128ddf 100755 --- a/include/models/service_model.h +++ b/include/models/service_model.h @@ -47,6 +47,8 @@ class ServiceModel { static std::unique_ptr get_service_by_id(pqxx::connection& db, const std::string& id, bool throw_when_null = false); + static std::vector> get_services_self(pqxx::connection& db, const std::string& creator_id, const std::string& status = ""); + static std::unique_ptr delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null = false); static bool update_service_by_id(pqxx::connection& db, const std::string id, const std::string tittle, const std::string description, const int price); diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index 54d0cb8..b969e77 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -202,6 +202,83 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & } } +void ServiceController::get_services_self(pqxx::connection &db, const crow::request &req, crow::response &res) { + try { + crow::json::rvalue get = crow::json::load(req.body); + std::string user_id = get["id"].s(); + + std::vector> all_services; + + auto status = req.url_params.get("status"); + + if (!status) { + all_services = ServiceModel::get_services_self(db, user_id); + + } else if (status && (std::string(status) == ServiceStatus::CLOSED || std::string(status) == ServiceStatus::OPEN)) { + all_services = ServiceModel::get_services_self(db, user_id, status); + } else { + handle_error(res, "status not valid value", 400); + return; + } + + crow::json::wvalue::list services; + for (unsigned int i = 0; i < all_services.size(); ++i) { + crow::json::wvalue service; + + service["id"] = all_services[i].get()->get_id(); + service["creator_id"] = all_services[i].get()->get_creator_id(); + if (all_services[i].get()->get_buyer_id().has_value()) + service["buyer_id"] = all_services[i].get()->get_buyer_id().value(); + service["title"] = all_services[i].get()->get_title(); + service["description"] = all_services[i].get()->get_description(); + service["price"] = all_services[i].get()->get_price(); + service["status"] = all_services[i].get()->get_status(); + service["type"] = all_services[i].get()->get_type(); + if (all_services[i].get()->get_image_url().has_value()) + service["image_url"] = all_services[i].get()->get_image_url().value(); + service["created_at"] = all_services[i].get()->get_created_at(); + service["updated_at"] = all_services[i].get()->get_updated_at(); + + if (all_services[i].get()->get_creator().has_value()) { + crow::json::wvalue creator; + creator["id"] = all_services[i].get()->get_creator().value().get_id(); + creator["username"] = all_services[i].get()->get_creator().value().get_username(); + creator["type"] = all_services[i].get()->get_creator().value().get_type(); + creator["email"] = all_services[i].get()->get_creator().value().get_email(); + creator["balance"] = all_services[i].get()->get_creator().value().get_balance(); + creator["created_at"] = all_services[i].get()->get_creator().value().get_created_at(); + creator["updated_at"] = all_services[i].get()->get_creator().value().get_updated_at(); + + service["creator"] = crow::json::wvalue(creator); + } + + if (all_services[i].get()->get_buyer().has_value()) { + crow::json::wvalue buyer; + buyer["id"] = all_services[i].get()->get_buyer().value().get_id(); + buyer["username"] = all_services[i].get()->get_buyer().value().get_username(); + buyer["type"] = all_services[i].get()->get_buyer().value().get_type(); + buyer["email"] = all_services[i].get()->get_buyer().value().get_email(); + buyer["balance"] = all_services[i].get()->get_buyer().value().get_balance(); + buyer["created_at"] = all_services[i].get()->get_buyer().value().get_created_at(); + buyer["updated_at"] = all_services[i].get()->get_buyer().value().get_updated_at(); + + service["buyer"] = crow::json::wvalue(buyer); + } + + services.push_back(service); + } + + crow::json::wvalue data{{"self_services", services}}; + + res.write(data.dump()); + res.code = 200; + res.end(); + } catch (const std::exception &e) { + std::cerr << "Error getting user services: " << e.what() << std::endl; + handle_error(res, "internal server error", 500); + } +} + void ServiceController::delete_service(pqxx::connection &db, const crow::request &req, crow::response &res, std::string service_id) { try { crow::json::rvalue request = crow::json::load(req.body); diff --git a/src/models/service_model.cpp b/src/models/service_model.cpp index 5fe7b9d..48c290c 100755 --- a/src/models/service_model.cpp +++ b/src/models/service_model.cpp @@ -216,6 +216,115 @@ std::unique_ptr ServiceModel::get_service_by_id(pqxx::connection& return get_service(db, "id", id, throw_when_null); } +std::vector> ServiceModel::get_services_self(pqxx::connection& db, const std::string& creator_id, const std::string& status) { + std::vector> all_services; + + pqxx::work txn(db); + + std::string query = + "SELECT s.id AS service_id, " + "s.creator_id, " + "s.buyer_id, " + "s.title, " + "s.description, " + "s.price, " + "s.status, " + "s.type, " + "s.image_url, " + "s.created_at, " + "s.updated_at, " + "uc.id AS creator_id, " + "uc.community_id AS creator_community_id, " + "uc.username AS creator_username, " + "uc.email AS creator_email, " + "uc.type AS creator_type, " + "uc.balance AS creator_balance, " + "uc.created_at AS creator_created_at, " + "uc.updated_at AS creator_updated_at, " + "ub.id AS buyer_id, " + "ub.community_id AS buyer_community_id, " + "ub.username AS buyer_username, " + "ub.email AS buyer_email, " + "ub.type AS buyer_type, " + "ub.balance AS buyer_balance, " + "ub.created_at AS buyer_created_at, " + "ub.updated_at AS buyer_updated_at " + "FROM services AS s " + "JOIN users AS uc ON s.creator_id = uc.id " + "LEFT JOIN users AS ub ON s.buyer_id = ub.id " + "WHERE s.creator_id = $1"; + + // Agregar filtro de status si se proporciona + if (!status.empty()) { + query += " AND s.status = $2"; + } + + pqxx::result result; + if (!status.empty()) { + result = txn.exec_params(query, creator_id, status); + } else { + result = txn.exec_params(query, creator_id); + } + + txn.commit(); + + for (const auto& row : result) { + std::optional buyer_id_field; + std::optional image_url_field; + if (!row["buyer_id"].is_null()) + buyer_id_field = row["buyer_id"].as(); + else + buyer_id_field = std::nullopt; + if (!row["image_url"].is_null()) + image_url_field = row["image_url"].as(); + else + image_url_field = std::nullopt; + + // Crear instancia de UserModel para el creador + UserModel creator( + row["creator_id"].as(), + row["creator_community_id"].as(), + row["creator_username"].as(), + row["creator_email"].as(), + row["creator_type"].as(), + row["creator_balance"].as(), + row["creator_created_at"].as(), + row["creator_updated_at"].as()); + + // Crear instancia de UserModel para el comprador, si existe + UserModel buyer; + if (buyer_id_field) { + buyer = UserModel( + row["buyer_id"].as(), + row["buyer_community_id"].as(), + row["buyer_username"].as(), + row["buyer_email"].as(), + row["buyer_type"].as(), + row["buyer_balance"].as(), + row["buyer_created_at"].as(), + row["buyer_updated_at"].as()); + } + + // Crear instancia de ServiceModel con UserModel como argumento adicional + all_services.push_back(std::make_unique( + row["service_id"].as(), + row["creator_id"].as(), + buyer_id_field, + row["title"].as(), + row["description"].as(), + row["price"].as(), + row["status"].as(), + row["type"].as(), + image_url_field, + creator, + buyer, + row["created_at"].as(), + row["updated_at"].as())); + } + + return all_services; +} + std::unique_ptr ServiceModel::delete_service_by_id(pqxx::connection& db, const std::string id, bool throw_when_null) { try { pqxx::work txn(db); diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 07c600c..5206400 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -11,6 +11,10 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { ServiceController::get_services(db, req, res); }); + CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + ServiceController::get_services_self(db, req, res); + }); + // ** GET /api/services/:id // ** POST /api/services diff --git a/test/tests/services/get_services_self_test.cpp b/test/tests/services/get_services_self_test.cpp new file mode 100644 index 0000000..6c64acb --- /dev/null +++ b/test/tests/services/get_services_self_test.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../common.h" + +class GetServicesSelfTest : public testing::Test { + protected: + std::string token1_; + std::string _user_id1; + std::string _user_id2; + nlohmann::json user1_ = { + {"email", "example@gmail.com"}, + {"username", "username"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + nlohmann::json user2_ = { + {"email", "example2@gmail.com"}, + {"username", "username2"}, + {"password", "Hola123."}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + void SetUp() override { + // create user1 + // create user 2 + // create services for user1 + // create services for user2 + } + + void TearDown() override { + // clear_user_table(); + // clear_community_table(); + // clear_services_table(); + } +}; + +// * void create_user_mock(nlohmann::json data); +//* void create_services_mock(std::string creator_id, int quantity); + +// * crear usuario y obtener su token, no crear servicios y solo existe una comunidad +// ? testear que devuelve un 200 y un servies array vacio + +// * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas +// ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: + +TEST(GetServicesSelfTest, get_all_services_self) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + + clean_user_table(); + clean_community_table(); +} From 0260985d022ed965e73b09594fed07c7a125073b Mon Sep 17 00:00:00 2001 From: leobelab Date: Wed, 8 May 2024 19:48:38 +0200 Subject: [PATCH 2/3] task #152: self view of created services done --- src/controllers/service_controller.cpp | 1 - src/routes/service_routes.cpp | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/controllers/service_controller.cpp b/src/controllers/service_controller.cpp index b969e77..1d5eb30 100755 --- a/src/controllers/service_controller.cpp +++ b/src/controllers/service_controller.cpp @@ -133,7 +133,6 @@ void ServiceController::get_services(pqxx::connection &db, const crow::request & std::vector> all_services; auto status = req.url_params.get("status"); - if (!status) { all_services = ServiceModel::get_services(db, user.get()->get_community_id()); diff --git a/src/routes/service_routes.cpp b/src/routes/service_routes.cpp index 5206400..c1a12c3 100755 --- a/src/routes/service_routes.cpp +++ b/src/routes/service_routes.cpp @@ -3,6 +3,9 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { // ** GET /api/services + CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { + ServiceController::get_services_self(db, req, res); + }); CROW_ROUTE(app, "/api/services/").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res, const std::string& service_id) { ServiceController::get_service_by_id(db, req, res, service_id); }); @@ -11,10 +14,6 @@ void initialize_service_routes(NebyApp& app, pqxx::connection& db) { ServiceController::get_services(db, req, res); }); - CROW_ROUTE(app, "/api/services/self").methods(crow::HTTPMethod::GET).CROW_MIDDLEWARES(app, VerifyJWT)([&db](const crow::request& req, crow::response& res) { - ServiceController::get_services_self(db, req, res); - }); - // ** GET /api/services/:id // ** POST /api/services From 76d248d8d83de37c966655583558f9e0c27d75cc Mon Sep 17 00:00:00 2001 From: leobelab Date: Sun, 12 May 2024 20:22:52 +0200 Subject: [PATCH 3/3] task #152: get-service-self-tested --- .../tests/services/get_services_self_test.cpp | 190 +++++++++++++++--- 1 file changed, 164 insertions(+), 26 deletions(-) diff --git a/test/tests/services/get_services_self_test.cpp b/test/tests/services/get_services_self_test.cpp index 6c64acb..572c62a 100644 --- a/test/tests/services/get_services_self_test.cpp +++ b/test/tests/services/get_services_self_test.cpp @@ -9,33 +9,105 @@ class GetServicesSelfTest : public testing::Test { protected: - std::string token1_; - std::string _user_id1; - std::string _user_id2; - nlohmann::json user1_ = { - {"email", "example@gmail.com"}, - {"username", "username"}, - {"password", "Hola123."}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; - nlohmann::json user2_ = { - {"email", "example2@gmail.com"}, - {"username", "username2"}, - {"password", "Hola123."}, - {"type", "admin"}, - {"community_name", "example_community_name"}}; + std::string _service_id_; + std::string _admin_id_; + std::string _admin_id2_; + std::string _admin_token_; + std::string _admin_token2_; + void register_admin() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example@gmail.com"}, + {"username", "example"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token_ = token_value; + } else { + _admin_token_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id_ = json["id"]; + } + void register_admin2() { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/auth/register"; + + nlohmann::json new_user = { + {"email", "example2@gmail.com"}, + {"username", "example2"}, + {"password", "P@ssw0rd!"}, + {"type", "admin"}, + {"community_name", "example_community_name"}}; + + auto response = cpr::Post(cpr::Url{url}, cpr::Body{new_user.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + + std::string set_cookie_header = response.header["Set-Cookie"]; + + size_t token_pos = set_cookie_header.find("token="); + if (token_pos != std::string::npos) { + size_t token_start = token_pos + 6; + size_t token_end = set_cookie_header.find(";", token_start); + std::string token_value = set_cookie_header.substr(token_start, token_end - token_start); + + _admin_token2_ = token_value; + } else { + _admin_token2_ = ""; + } + + auto json = nlohmann::json::parse(response.text); + _admin_id2_ = json["id"]; + } void SetUp() override { - // create user1 - // create user 2 - // create services for user1 - // create services for user2 + register_admin(); + std::string url_service = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services"; + nlohmann::json new_service1 = { + {"title", "nodeberiaexistir1"}, + {"description", "some description 1"}, + {"price", 40}, + {"type", "offered"}}; + auto s_create1 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service1.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + auto json = nlohmann::json::parse(s_create1.text); + _service_id_ = json["id"]; + nlohmann::json new_service2 = { + {"title", "nodeberiaexistir2"}, + {"description", "some description 2"}, + {"price", 50}, + {"type", "offered"}}; + auto s_create2 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service2.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + nlohmann::json new_service3 = { + {"title", "nodeberiaexistir3"}, + {"description", "some description 3"}, + {"price", 60}, + {"type", "offered"}}; + auto s_create3 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service3.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + nlohmann::json new_service4 = { + {"title", "nodeberiaexistir4"}, + {"description", "some description 4"}, + {"price", 70}, + {"type", "offered"}}; + auto s_create4 = cpr::Post(cpr::Url{url_service}, cpr::Cookies{{"token", _admin_token_}}, cpr::Body{new_service4.dump()}, cpr::Header{{"Content-Type", "application/json"}}); + sleep(1); + register_admin2(); } void TearDown() override { - // clear_user_table(); - // clear_community_table(); - // clear_services_table(); + clean_community_table(); + clean_user_table(); + // clean_service_table(); } }; @@ -48,10 +120,10 @@ class GetServicesSelfTest : public testing::Test { // * crear usuario y obtener su token, existen servicios de ese usuario y solo existe una comunidas // ? testear un 200 y services no es vacio, tiene servicios, comprobar que tiene las propiedades: -TEST(GetServicesSelfTest, get_all_services_self) { +TEST_F(GetServicesSelfTest, get_all_services_self) { std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; - auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", register_and_get_user_token()}}); + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; @@ -60,7 +132,73 @@ TEST(GetServicesSelfTest, get_all_services_self) { ASSERT_TRUE(json.contains("self_services")); ASSERT_TRUE(json["self_services"].is_array()); + ASSERT_EQ(json["self_services"].size(), 4); +} + +TEST_F(GetServicesSelfTest, get_all_services_self_closed) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=closed"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "closed"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_open) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=open"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); - clean_user_table(); - clean_community_table(); + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "open"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_wrong_status) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self?status=NON_EXISTENT_STATUS"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 400) << "status not valid value"; + + auto json = nlohmann::json::parse(response.text); + + EXPECT_TRUE(json.contains("error")); + EXPECT_EQ(json["error"], "status not valid value"); + for (const auto& self_service : json["self_services"]) { + ASSERT_TRUE(self_service.contains("status")); + ASSERT_EQ(self_service["status"], "closed"); + } +} + +TEST_F(GetServicesSelfTest, get_all_services_self_empty_list) { + std::string url = "http://backend:" + std::to_string(HTTP_PORT) + "/api/services/self"; + + auto response = cpr::Get(cpr::Url{url}, cpr::Cookies{{"token", _admin_token2_}}, cpr::Header{{"Content-Type", "application/json"}}); + + EXPECT_EQ(response.status_code, 200) << "expect 200 status code"; + + auto json = nlohmann::json::parse(response.text); + + ASSERT_TRUE(json.contains("self_services")); + + ASSERT_TRUE(json["self_services"].is_array()); + ASSERT_TRUE(json["sel_services"].empty()); }