diff --git a/CMakeLists.txt b/CMakeLists.txt index d0097dd..ccb46f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(project_lower vweb) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUGSOFT") +option(BUILD_TESTS "Build tests" OFF) + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -46,4 +48,24 @@ configure_file( install(FILES ${PROJECT_BINARY_DIR}/pkg/${project_lower}-config.cmake ${PROJECT_BINARY_DIR}/${project_lower}-config-version.cmake - DESTINATION lib/${PROJECT_NAME}-${version}) \ No newline at end of file + DESTINATION lib/${PROJECT_NAME}-${version}) + +if (BUILD_TESTS) + add_subdirectory(Tests) + get_property(test_sources GLOBAL PROPERTY Test_SRCS) + set(TESTS_SRCS ${test_sources}) + + Include(FetchContent) + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.4.0 + ) + FetchContent_MakeAvailable(Catch2) + add_executable(tests ${SOURCE_FILES} ${TESTS_SRCS}) + target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) + list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) + include(CTest) + include(Catch) + catch_discover_tests(tests WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Tests) +endif () \ No newline at end of file diff --git a/CMakeMacros.txt b/CMakeMacros.txt index bea11db..7dfe23e 100644 --- a/CMakeMacros.txt +++ b/CMakeMacros.txt @@ -22,4 +22,17 @@ macro(add_headers) endif () endforeach () set_property(GLOBAL PROPERTY Headers ${tmp}) +endmacro() + +macro(add_tests) + get_property(tmp GLOBAL PROPERTY Test_SRCS) + file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + foreach (_src ${ARGN}) + if (_relPath) + list(APPEND tmp "${_relPath}/${_src}") + else () + list(APPEND tmp "${_src}") + endif () + endforeach () + set_property(GLOBAL PROPERTY Test_SRCS ${tmp}) endmacro() \ No newline at end of file diff --git a/Includes/RequestHandler.h b/Includes/RequestHandler.h index 152f948..6108bd8 100644 --- a/Includes/RequestHandler.h +++ b/Includes/RequestHandler.h @@ -21,6 +21,7 @@ public: void Stop(); void SetRouter(Ref &router); void AddSendResponse(SendData); + void CloseRequest(int id) const; Ref &Middleware() { return m_MiddleWare; } bool HasBodyParser(const std::string &key) { return m_ParseFunctions.contains(key); diff --git a/Source/RequestHandler.cpp b/Source/RequestHandler.cpp index 2d8a132..76c8560 100644 --- a/Source/RequestHandler.cpp +++ b/Source/RequestHandler.cpp @@ -123,6 +123,10 @@ struct RequestJob : public WorkerJob { MRequestHandler->AddSendResponse( {0, (ssize_t)content.size(), MRequest->ID, content}); MMiddleWareHandler->Shutdown(MRequest, response); + } catch (const std::bad_alloc &e) { + // We are out of memory... try to clean up some request... + SocketUtils::Close(MRequest->ID); + MRequestHandler->CloseRequest(MRequest->ID); } catch (const std::exception &e) { fprintf(stderr, "[VWEB] >> Failed to handle Requests... Error thrown\n%s\n", @@ -157,8 +161,11 @@ void RequestHandler::AddSendResponse(SendData sendData) { auto id = sendData.SocketID; m_Server->m_OutRequest.Add(id, sendData); if (!m_SocketManager->SetSendListen(id)) { - SocketUtils::Close(id); - m_Server->m_OutRequest.Remove(id); + CloseRequest(id); } } +void RequestHandler::CloseRequest(const int id) const { + SocketUtils::Close(id); + m_Server->m_OutRequest.Remove(id); +} } // namespace VWeb diff --git a/Source/Router.cpp b/Source/Router.cpp index 08ab579..edc8539 100644 --- a/Source/Router.cpp +++ b/Source/Router.cpp @@ -156,28 +156,23 @@ struct InlineRoute : Route { }; void Router::Get(const std::string &path, const RouteFunction &func) { - m_Tree.Add(s_HttpMethodToString[HttpMethod::GET] + path, - (uint32_t)HttpMethod::GET, + m_Tree.Add(path, static_cast(HttpMethod::GET), [func] { return std::make_shared(func); }); } void Router::Post(const std::string &path, const RouteFunction &func) { - m_Tree.Add(s_HttpMethodToString[HttpMethod::POST] + path, - (uint32_t)HttpMethod::POST, + m_Tree.Add(path, static_cast(HttpMethod::POST), [func] { return std::make_shared(func); }); } void Router::Put(const std::string &path, const RouteFunction &func) { - m_Tree.Add(s_HttpMethodToString[HttpMethod::PUT] + path, - (uint32_t)HttpMethod::PUT, + m_Tree.Add(path, static_cast(HttpMethod::PUT), [func] { return std::make_shared(func); }); } void Router::Patch(const std::string &path, const RouteFunction &func) { - m_Tree.Add(s_HttpMethodToString[HttpMethod::PATCH] + path, - (uint32_t)HttpMethod::PATCH, + m_Tree.Add(path, static_cast(HttpMethod::PATCH), [func] { return std::make_shared(func); }); } void Router::Delete(const std::string &path, const RouteFunction &func) { - m_Tree.Add(s_HttpMethodToString[HttpMethod::DELETE] + path, - (uint32_t)HttpMethod::DELETE, + m_Tree.Add(path, static_cast(HttpMethod::DELETE), [func] { return std::make_shared(func); }); } } // namespace VWeb diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 0000000..465d207 --- /dev/null +++ b/Tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_tests( + Router.cpp +) \ No newline at end of file diff --git a/Tests/Router.cpp b/Tests/Router.cpp new file mode 100644 index 0000000..8e656f9 --- /dev/null +++ b/Tests/Router.cpp @@ -0,0 +1,84 @@ +#include "Includes/Router.h" + +#include "Includes/Request.h" +#include "Includes/Response.h" + +#include + +namespace VWeb { + +class FailureRoute final : public Route { +public: + bool IsAllowed(Request &request) override { return false; } +}; + +TEST_CASE("route_that_exists_returns_route", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/test"; + router.Register("/test"); + + REQUIRE(router.FindRoute(request) != nullptr); +} + +TEST_CASE("route_that_not_exists_returns_nullptr", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/testre"; + router.Register("/test"); + + REQUIRE(router.FindRoute(request) == nullptr); +} + +TEST_CASE("route_matches_exactly", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/test/id"; + router.Register("/test/:id"); + router.Register("/test/id"); + + auto route = router.FindRoute(request); + REQUIRE(route); + REQUIRE(route->IsAllowed(*request)); +} + +TEST_CASE("route_can_handle_variants", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/test/1"; + router.Register("/test/id"); + router.Register("/test/:id"); + + auto route = router.FindRoute(request); + REQUIRE(route); + REQUIRE(route->IsAllowed(*request)); +} + +TEST_CASE("router_returns_function_instead_of_class", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/test/id"; + router.Register("/test/:id"); + router.Get("/test/id", [](Request &, Response &) { + return true; + }); + + auto route = router.FindRoute(request); + REQUIRE(route); + REQUIRE(route->IsAllowed(*request)); +} + +TEST_CASE("router_returns_class_if_function_is_provided", "[Router]") { + Router router; + Ref request = CreateRef(); + request->URI = "/test/1"; + router.Register("/test/:id"); + router.Get("/test/id", [](Request &, Response &) { + return true; + }); + + auto route = router.FindRoute(request); + REQUIRE(route); + REQUIRE_FALSE(route->IsAllowed(*request)); +} +} // namespace VWeb \ No newline at end of file