diff --git a/src/agent/centralized_configuration/CMakeLists.txt b/src/agent/centralized_configuration/CMakeLists.txt index 75c4302078..32606aca3d 100644 --- a/src/agent/centralized_configuration/CMakeLists.txt +++ b/src/agent/centralized_configuration/CMakeLists.txt @@ -10,7 +10,7 @@ find_package(nlohmann_json REQUIRED) add_library(CentralizedConfiguration src/centralized_configuration.cpp) target_include_directories(CentralizedConfiguration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_link_libraries(CentralizedConfiguration PUBLIC ModuleCommand Boost::asio nlohmann_json::nlohmann_json Config PRIVATE Logger) +target_link_libraries(CentralizedConfiguration PUBLIC ModuleCommand Boost::asio nlohmann_json::nlohmann_json Config FilesystemWrapper PRIVATE Logger) include(../../cmake/ConfigureTarget.cmake) configure_target(CentralizedConfiguration) diff --git a/src/agent/centralized_configuration/include/centralized_configuration.hpp b/src/agent/centralized_configuration/include/centralized_configuration.hpp index 08965d3481..5b0ec50478 100644 --- a/src/agent/centralized_configuration/include/centralized_configuration.hpp +++ b/src/agent/centralized_configuration/include/centralized_configuration.hpp @@ -6,7 +6,9 @@ #include +#include #include +#include #include #include @@ -23,6 +25,14 @@ namespace centralized_configuration using ValidateFileFunctionType = std::function; using ReloadModulesFunctionType = std::function; + /// @brief Constructor that allows injecting a file system wrapper. + /// @param fileSystemWrapper An optional filesystem wrapper. If nullptr, it will use FileSystemWrapper + explicit CentralizedConfiguration(std::shared_ptr fileSystemWrapper = nullptr) + : m_fileSystemWrapper(fileSystemWrapper ? fileSystemWrapper + : std::make_shared()) + { + } + /// @brief Executes a command for the centralized configuration system. /// @param command A string containing a JSON command to execute. /// @param parameters A json object containing the parameters of the command to be executed. @@ -58,6 +68,9 @@ namespace centralized_configuration void ReloadModulesFunction(ReloadModulesFunctionType reloadModulesFunction); private: + /// @brief Member to interact with the file system. + std::shared_ptr m_fileSystemWrapper; + /// @brief Function to set group IDs. SetGroupIdFunctionType m_setGroupIdFunction; diff --git a/src/agent/centralized_configuration/src/centralized_configuration.cpp b/src/agent/centralized_configuration/src/centralized_configuration.cpp index be3e344711..284b666898 100644 --- a/src/agent/centralized_configuration/src/centralized_configuration.cpp +++ b/src/agent/centralized_configuration/src/centralized_configuration.cpp @@ -41,11 +41,12 @@ namespace centralized_configuration { std::filesystem::path sharedDirPath(config::DEFAULT_SHARED_CONFIG_PATH); - if (std::filesystem::exists(sharedDirPath) && std::filesystem::is_directory(sharedDirPath)) + if (m_fileSystemWrapper->exists(sharedDirPath) && + m_fileSystemWrapper->is_directory(sharedDirPath)) { for (const auto& entry : std::filesystem::directory_iterator(sharedDirPath)) { - std::filesystem::remove_all(entry); + m_fileSystemWrapper->remove_all(entry); } } } @@ -91,7 +92,7 @@ namespace centralized_configuration for (const auto& groupId : groupIds) { const std::filesystem::path tmpGroupFile = - std::filesystem::temp_directory_path() / (groupId + config::DEFAULT_SHARED_FILE_EXTENSION); + m_fileSystemWrapper->temp_directory_path() / (groupId + config::DEFAULT_SHARED_FILE_EXTENSION); m_downloadGroupFilesFunction(groupId, tmpGroupFile.string()); if (!m_validateFileFunction(tmpGroupFile)) { @@ -101,18 +102,14 @@ namespace centralized_configuration try { - if (std::filesystem::exists(tmpGroupFile) && - tmpGroupFile.parent_path() == std::filesystem::temp_directory_path()) + if (m_fileSystemWrapper->exists(tmpGroupFile) && + tmpGroupFile.parent_path() == m_fileSystemWrapper->temp_directory_path()) { - if (!std::filesystem::remove(tmpGroupFile)) + if (!m_fileSystemWrapper->remove(tmpGroupFile)) { LogWarn("Failed to delete invalid group file: {}", tmpGroupFile.string()); } } - else - { - LogWarn("Skipping deletion of suspicious file path: {}", tmpGroupFile.string()); - } } catch (const std::filesystem::filesystem_error& e) { @@ -131,8 +128,8 @@ namespace centralized_configuration try { - std::filesystem::create_directories(destGroupFile.parent_path()); - std::filesystem::rename(tmpGroupFile, destGroupFile); + m_fileSystemWrapper->create_directories(destGroupFile.parent_path()); + m_fileSystemWrapper->rename(tmpGroupFile, destGroupFile); } catch (const std::filesystem::filesystem_error& e) { diff --git a/src/agent/centralized_configuration/tests/CMakeLists.txt b/src/agent/centralized_configuration/tests/CMakeLists.txt index 5790ab5b19..3330e8e300 100644 --- a/src/agent/centralized_configuration/tests/CMakeLists.txt +++ b/src/agent/centralized_configuration/tests/CMakeLists.txt @@ -2,5 +2,5 @@ find_package(GTest CONFIG REQUIRED) add_executable(centralized_configuration_test centralized_configuration_tests.cpp) configure_target(centralized_configuration_test) -target_link_libraries(centralized_configuration_test PRIVATE CentralizedConfiguration GTest::gtest GTest::gmock) +target_link_libraries(centralized_configuration_test PRIVATE CentralizedConfiguration GTest::gtest GTest::gmock GTest::gmock_main) add_test(NAME CentralizedConfiguration COMMAND centralized_configuration_test) diff --git a/src/agent/centralized_configuration/tests/centralized_configuration_tests.cpp b/src/agent/centralized_configuration/tests/centralized_configuration_tests.cpp index f425bad276..12927bdca4 100644 --- a/src/agent/centralized_configuration/tests/centralized_configuration_tests.cpp +++ b/src/agent/centralized_configuration/tests/centralized_configuration_tests.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -13,7 +14,11 @@ #include #include +#include +#include + using centralized_configuration::CentralizedConfiguration; +using namespace testing; namespace { @@ -21,15 +26,29 @@ namespace boost::asio::awaitable TestExecuteCommand(CentralizedConfiguration& centralizedConfiguration, const std::string& command, const nlohmann::json& parameters, - module_command::Status expectedErrorCode) + module_command::Status expectedErrorCode, + const std::string& expectedMessage) { const auto commandResult = co_await centralizedConfiguration.ExecuteCommand(command, parameters); EXPECT_EQ(commandResult.ErrorCode, expectedErrorCode); + EXPECT_EQ(commandResult.Message, expectedMessage); } // NOLINTEND(cppcoreguidelines-avoid-reference-coroutine-parameters) } // namespace +class MockFileSystem : public IFileSystem +{ +public: + MOCK_METHOD(bool, exists, (const std::filesystem::path& path), (const, override)); + MOCK_METHOD(bool, is_directory, (const std::filesystem::path& path), (const, override)); + MOCK_METHOD(std::uintmax_t, remove_all, (const std::filesystem::path& path), (override)); + MOCK_METHOD(std::filesystem::path, temp_directory_path, (), (const, override)); + MOCK_METHOD(bool, create_directories, (const std::filesystem::path& path), (override)); + MOCK_METHOD(void, rename, (const std::filesystem::path& from, const std::filesystem::path& to), (override)); + MOCK_METHOD(bool, remove, (const std::filesystem::path& path), (override)); +}; + TEST(CentralizedConfiguration, Constructor) { EXPECT_NO_THROW([[maybe_unused]] CentralizedConfiguration centralizedConfiguration); @@ -44,8 +63,11 @@ TEST(CentralizedConfiguration, ExecuteCommandReturnsFailureOnUnrecognizedCommand []() -> boost::asio::awaitable { CentralizedConfiguration centralizedConfiguration; - co_await TestExecuteCommand( - centralizedConfiguration, "unknown-command", {}, module_command::Status::FAILURE); + co_await TestExecuteCommand(centralizedConfiguration, + "unknown-command", + {}, + module_command::Status::FAILURE, + "CentralizedConfiguration command not recognized"); }(), boost::asio::detached); @@ -61,7 +83,16 @@ TEST(CentralizedConfiguration, ExecuteCommandReturnsFailureOnEmptyList) []() -> boost::asio::awaitable { CentralizedConfiguration centralizedConfiguration; - co_await TestExecuteCommand(centralizedConfiguration, "set-group", {}, module_command::Status::FAILURE); + centralizedConfiguration.SetGroupIdFunction([](const std::vector&) { return true; }); + centralizedConfiguration.SetDownloadGroupFilesFunction([](const std::string&, const std::string&) + { return true; }); + centralizedConfiguration.ValidateFileFunction([](const std::filesystem::path&) { return true; }); + centralizedConfiguration.ReloadModulesFunction([]() {}); + co_await TestExecuteCommand(centralizedConfiguration, + "set-group", + {}, + module_command::Status::FAILURE, + "CentralizedConfiguration group set failed, no group list"); }(), boost::asio::detached); @@ -77,10 +108,18 @@ TEST(CentralizedConfiguration, ExecuteCommandReturnsFailureOnParseParameters) []() -> boost::asio::awaitable { CentralizedConfiguration centralizedConfiguration; + centralizedConfiguration.SetGroupIdFunction([](const std::vector&) { return true; }); + centralizedConfiguration.SetDownloadGroupFilesFunction([](const std::string&, const std::string&) + { return true; }); + centralizedConfiguration.ValidateFileFunction([](const std::filesystem::path&) { return true; }); + centralizedConfiguration.ReloadModulesFunction([]() {}); const std::vector parameterList = {true, "group2"}; - co_await TestExecuteCommand( - centralizedConfiguration, "set-group", parameterList, module_command::Status::FAILURE); + co_await TestExecuteCommand(centralizedConfiguration, + "set-group", + parameterList, + module_command::Status::FAILURE, + "CentralizedConfiguration error while parsing parameters"); }(), boost::asio::detached); @@ -95,21 +134,44 @@ TEST(CentralizedConfiguration, ExecuteCommandHandlesRecognizedCommands) io_context, []() -> boost::asio::awaitable { - CentralizedConfiguration centralizedConfiguration; + auto mockFileSystem = std::make_shared(); + + EXPECT_CALL(*mockFileSystem, exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, is_directory(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove_all(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mockFileSystem, temp_directory_path()) + .WillRepeatedly(Return(std::filesystem::temp_directory_path())); + EXPECT_CALL(*mockFileSystem, create_directories(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, rename(_, _)).WillRepeatedly(Return()); + + CentralizedConfiguration centralizedConfiguration(std::move(mockFileSystem)); centralizedConfiguration.SetGroupIdFunction([](const std::vector&) { return true; }); centralizedConfiguration.GetGroupIdFunction([]() { return std::vector {"group1", "group2"}; }); centralizedConfiguration.SetDownloadGroupFilesFunction([](const std::string&, const std::string&) { return true; }); + centralizedConfiguration.ValidateFileFunction([](const std::filesystem::path&) { return true; }); + centralizedConfiguration.ReloadModulesFunction([]() {}); const nlohmann::json groupsList = nlohmann::json::parse(R"([["group1", "group2"]])"); - co_await TestExecuteCommand( - centralizedConfiguration, "set-group", groupsList, module_command::Status::SUCCESS); - - co_await TestExecuteCommand(centralizedConfiguration, "update-group", {}, module_command::Status::SUCCESS); - - co_await TestExecuteCommand( - centralizedConfiguration, "unknown-command", {}, module_command::Status::FAILURE); + co_await TestExecuteCommand(centralizedConfiguration, + "set-group", + groupsList, + module_command::Status::SUCCESS, + "CentralizedConfiguration 'set-group' done."); + + co_await TestExecuteCommand(centralizedConfiguration, + "update-group", + {}, + module_command::Status::SUCCESS, + "CentralizedConfiguration 'update-group' done."); + + co_await TestExecuteCommand(centralizedConfiguration, + "unknown-command", + {}, + module_command::Status::FAILURE, + "CentralizedConfiguration command not recognized"); }(), boost::asio::detached); @@ -124,12 +186,20 @@ TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForS io_context, []() -> boost::asio::awaitable { - CentralizedConfiguration centralizedConfiguration; + auto mockFileSystem = std::make_shared(); - const nlohmann::json groupsList = nlohmann::json::parse(R"([["group1", "group2"]])"); + EXPECT_CALL(*mockFileSystem, exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, is_directory(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove_all(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mockFileSystem, temp_directory_path()) + .WillRepeatedly(Return(std::filesystem::temp_directory_path())); + EXPECT_CALL(*mockFileSystem, create_directories(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, rename(_, _)).WillRepeatedly(Return()); + + CentralizedConfiguration centralizedConfiguration(std::move(mockFileSystem)); - co_await TestExecuteCommand( - centralizedConfiguration, "set-group", groupsList, module_command::Status::FAILURE); + const nlohmann::json groupsList = nlohmann::json::parse(R"([["group1", "group2"]])"); bool wasSetGroupIdFunctionCalled = false; bool wasDownloadGroupFilesFunctionCalled = false; @@ -148,11 +218,17 @@ TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForS return wasDownloadGroupFilesFunctionCalled; }); + centralizedConfiguration.ValidateFileFunction([](const std::filesystem::path&) { return true; }); + centralizedConfiguration.ReloadModulesFunction([]() {}); + EXPECT_FALSE(wasSetGroupIdFunctionCalled); EXPECT_FALSE(wasDownloadGroupFilesFunctionCalled); - co_await TestExecuteCommand( - centralizedConfiguration, "set-group", groupsList, module_command::Status::SUCCESS); + co_await TestExecuteCommand(centralizedConfiguration, + "set-group", + groupsList, + module_command::Status::SUCCESS, + "CentralizedConfiguration 'set-group' done."); EXPECT_TRUE(wasSetGroupIdFunctionCalled); EXPECT_TRUE(wasDownloadGroupFilesFunctionCalled); @@ -170,9 +246,18 @@ TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForU io_context, []() -> boost::asio::awaitable { - CentralizedConfiguration centralizedConfiguration; + auto mockFileSystem = std::make_shared(); - co_await TestExecuteCommand(centralizedConfiguration, "update-group", {}, module_command::Status::FAILURE); + EXPECT_CALL(*mockFileSystem, exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, is_directory(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove_all(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mockFileSystem, temp_directory_path()) + .WillRepeatedly(Return(std::filesystem::temp_directory_path())); + EXPECT_CALL(*mockFileSystem, create_directories(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, remove(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockFileSystem, rename(_, _)).WillRepeatedly(Return()); + + CentralizedConfiguration centralizedConfiguration(std::move(mockFileSystem)); bool wasGetGroupIdFunctionCalled = false; bool wasDownloadGroupFilesFunctionCalled = false; @@ -191,10 +276,17 @@ TEST(CentralizedConfiguration, SetFunctionsAreCalledAndReturnsCorrectResultsForU return wasDownloadGroupFilesFunctionCalled; }); + centralizedConfiguration.ValidateFileFunction([](const std::filesystem::path&) { return true; }); + centralizedConfiguration.ReloadModulesFunction([]() {}); + EXPECT_FALSE(wasGetGroupIdFunctionCalled); EXPECT_FALSE(wasDownloadGroupFilesFunctionCalled); - co_await TestExecuteCommand(centralizedConfiguration, "update-group", {}, module_command::Status::SUCCESS); + co_await TestExecuteCommand(centralizedConfiguration, + "update-group", + {}, + module_command::Status::SUCCESS, + "CentralizedConfiguration 'update-group' done."); EXPECT_TRUE(wasGetGroupIdFunctionCalled); EXPECT_TRUE(wasDownloadGroupFilesFunctionCalled); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e513e650a8..989c5ff49d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(data_provider) add_subdirectory(dbsync) add_subdirectory(error_messages) add_subdirectory(file_op) +add_subdirectory(filesystem_wrapper) add_subdirectory(hashHelper) add_subdirectory(logger) add_subdirectory(mem_op) diff --git a/src/common/filesystem_wrapper/CMakeLists.txt b/src/common/filesystem_wrapper/CMakeLists.txt new file mode 100644 index 0000000000..3759d36a39 --- /dev/null +++ b/src/common/filesystem_wrapper/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.22) + +set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/../../vcpkg/scripts/buildsystems/vcpkg.cmake") +set(VCPKG_MANIFEST_DIR ${CMAKE_SOURCE_DIR}/../../) + +project(FilesystemWrapper) + +include(../../cmake/CommonSettings.cmake) +set_common_settings() + +add_library(FilesystemWrapper STATIC + src/filesystem_wrapper.cpp +) + +target_include_directories(FilesystemWrapper PUBLIC include) diff --git a/src/common/filesystem_wrapper/include/filesystem_wrapper.hpp b/src/common/filesystem_wrapper/include/filesystem_wrapper.hpp new file mode 100644 index 0000000000..5aea65670c --- /dev/null +++ b/src/common/filesystem_wrapper/include/filesystem_wrapper.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace filesystem_wrapper +{ + /// @brief A wrapper class for file system operations, implementing the IFileSystem interface. + /// + /// This class provides methods for file system operations such as checking if a file exists, + /// removing directories, creating directories, and renaming files, among others. It is designed + /// to be used as a concrete implementation of the IFileSystem interface, encapsulating the actual + /// file system operations. + class FileSystemWrapper : public IFileSystem { + public: + /// @brief Checks if the specified path exists in the file system. + /// @param path The path to check. + /// @return Returns true if the path exists, otherwise false. + bool exists(const std::filesystem::path& path) const override; + + /// @brief Checks if the specified path is a directory. + /// @param path The path to check. + /// @return Returns true if the path is a directory, otherwise false. + bool is_directory(const std::filesystem::path& path) const override; + + /// @brief Removes all files and subdirectories in the specified directory. + /// @param path The directory path to remove. + /// @return Returns the number of files and directories removed. + std::uintmax_t remove_all(const std::filesystem::path& path) override; + + /// @brief Retrieves the system's temporary directory path. + /// @return Returns the path of the system's temporary directory. + std::filesystem::path temp_directory_path() const override; + + /// @brief Creates a new directory at the specified path. + /// @param path The path of the directory to create. + /// @return Returns true if the directory was successfully created, otherwise false. + bool create_directories(const std::filesystem::path& path) override; + + /// @brief Renames a file or directory from the 'from' path to the 'to' path. + /// @param from The current path of the file or directory. + /// @param to The new path for the file or directory. + void rename(const std::filesystem::path& from, const std::filesystem::path& to) override; + + /// @brief Removes the specified file or directory. + /// @param path The file or directory to remove. + /// @return Returns true if the file or directory was successfully removed, otherwise false. + bool remove(const std::filesystem::path& path) override; + }; +} diff --git a/src/common/filesystem_wrapper/include/ifilesystem.hpp b/src/common/filesystem_wrapper/include/ifilesystem.hpp new file mode 100644 index 0000000000..04a4b48782 --- /dev/null +++ b/src/common/filesystem_wrapper/include/ifilesystem.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +/// @brief Interface for file system operations. +/// +/// This interface defines a set of file system operations such as checking if a file exists, +/// removing files or directories, creating directories, and renaming files. Any concrete class +/// that implements this interface will be expected to provide the actual functionality for these +/// operations. This allows for abstraction and easier testing or swapping of file system implementations. +class IFileSystem { +public: + /// @brief Virtual destructor for IFileSystem. + /// + /// Ensures that any derived classes with their own resources are correctly cleaned up. + virtual ~IFileSystem() = default; + + /// @brief Checks if the specified path exists in the file system. + /// @param path The path to check. + /// @return Returns true if the path exists, otherwise false. + virtual bool exists(const std::filesystem::path& path) const = 0; + + /// @brief Checks if the specified path is a directory. + /// @param path The path to check. + /// @return Returns true if the path is a directory, otherwise false. + virtual bool is_directory(const std::filesystem::path& path) const = 0; + + /// @brief Removes all files and subdirectories in the specified directory. + /// @param path The directory path to remove. + /// @return Returns the number of files and directories removed. + virtual std::uintmax_t remove_all(const std::filesystem::path& path) = 0; + + /// @brief Retrieves the system's temporary directory path. + /// @return Returns the path of the system's temporary directory. + virtual std::filesystem::path temp_directory_path() const = 0; + + /// @brief Creates a new directory at the specified path. + /// @param path The path of the directory to create. + /// @return Returns true if the directory was successfully created, otherwise false. + virtual bool create_directories(const std::filesystem::path& path) = 0; + + /// @brief Renames a file or directory from the 'from' path to the 'to' path. + /// @param from The current path of the file or directory. + /// @param to The new path for the file or directory. + virtual void rename(const std::filesystem::path& from, const std::filesystem::path& to) = 0; + + /// @brief Removes the specified file or directory. + /// @param path The file or directory to remove. + /// @return Returns true if the file or directory was successfully removed, otherwise false. + virtual bool remove(const std::filesystem::path& path) = 0; +}; diff --git a/src/common/filesystem_wrapper/src/filesystem_wrapper.cpp b/src/common/filesystem_wrapper/src/filesystem_wrapper.cpp new file mode 100644 index 0000000000..5ffe3c2d5c --- /dev/null +++ b/src/common/filesystem_wrapper/src/filesystem_wrapper.cpp @@ -0,0 +1,41 @@ +#include +#include + + +namespace filesystem_wrapper +{ + bool FileSystemWrapper::exists(const std::filesystem::path& path) const + { + return std::filesystem::exists(path); + } + + bool FileSystemWrapper::is_directory(const std::filesystem::path& path) const + { + return std::filesystem::is_directory(path); + } + + std::uintmax_t FileSystemWrapper::remove_all(const std::filesystem::path& path) + { + return std::filesystem::remove_all(path); + } + + std::filesystem::path FileSystemWrapper::temp_directory_path() const + { + return std::filesystem::temp_directory_path(); + } + + bool FileSystemWrapper::create_directories(const std::filesystem::path& path) + { + return std::filesystem::create_directories(path); + } + + void FileSystemWrapper::rename(const std::filesystem::path& from, const std::filesystem::path& to) + { + std::filesystem::rename(from, to); + } + + bool FileSystemWrapper::remove(const std::filesystem::path& path) + { + return std::filesystem::remove(path); + } +}