#include "infrastructure/repositories/FileUserRepository.h" #include "domain/entities/User.h" #include #include #include #include #include using namespace nxl::autostore; using Catch::Matchers::Equals; namespace Test { // Constants for magic strings and numbers constexpr const char* TEST_USER_ID_1 = "user123"; constexpr const char* TEST_USER_ID_2 = "user456"; constexpr const char* TEST_USERNAME_1 = "testuser"; constexpr const char* TEST_USERNAME_2 = "anotheruser"; constexpr const char* TEST_PASSWORD_HASH_1 = "hashedpassword123"; constexpr const char* TEST_PASSWORD_HASH_2 = "hashedpassword456"; constexpr const char* NON_EXISTENT_ID = "nonexistent"; constexpr const char* NON_EXISTENT_USERNAME = "nonexistentuser"; constexpr const char* TEST_DIR_NAME = "autostore_test"; constexpr const char* TEST_DB_FILE_NAME = "test_users.json"; // Helper function to create a test user with default values domain::User createTestUser(const std::string& id = TEST_USER_ID_1, const std::string& username = TEST_USERNAME_1, const std::string& passwordHash = TEST_PASSWORD_HASH_1) { domain::User user; user.id = id; user.username = username; user.passwordHash = passwordHash; return user; } // Helper function to create a second test user domain::User createSecondTestUser() { return createTestUser(TEST_USER_ID_2, TEST_USERNAME_2, TEST_PASSWORD_HASH_2); } // Helper function to set up test environment std::string setupTestEnvironment() { std::filesystem::path testDir = std::filesystem::temp_directory_path() / TEST_DIR_NAME; std::filesystem::create_directories(testDir); std::string testDbPath = (testDir / TEST_DB_FILE_NAME).string(); // Clean up any existing test file if (std::filesystem::exists(testDbPath)) { std::filesystem::remove(testDbPath); } return testDbPath; } // Helper function to clean up test environment void cleanupTestEnvironment() { std::filesystem::path testDir = std::filesystem::temp_directory_path() / TEST_DIR_NAME; if (std::filesystem::exists(testDir)) { std::filesystem::remove_all(testDir); } } // Helper function to verify user properties match expected values void verifyUserProperties(const domain::User& user, const std::string& expectedId, const std::string& expectedUsername, const std::string& expectedPasswordHash) { REQUIRE(user.id == expectedId); REQUIRE(user.username == expectedUsername); REQUIRE(user.passwordHash == expectedPasswordHash); } // Helper function to verify user properties match default test user values void verifyDefaultTestUser(const domain::User& user) { verifyUserProperties(user, TEST_USER_ID_1, TEST_USERNAME_1, TEST_PASSWORD_HASH_1); } // Helper function to verify user properties match second test user values void verifySecondTestUser(const domain::User& user) { verifyUserProperties(user, TEST_USER_ID_2, TEST_USERNAME_2, TEST_PASSWORD_HASH_2); } } // namespace Test TEST_CASE("FileUserRepository Integration Tests", "[integration][FileUserRepository]") { // Setup test environment std::string testDbPath = Test::setupTestEnvironment(); SECTION("when a new user is saved then it can be found by id") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); // When repository.save(testUser); // Then auto foundUser = repository.findById(Test::TEST_USER_ID_1); REQUIRE(foundUser.has_value()); Test::verifyDefaultTestUser(*foundUser); } SECTION("when a new user is saved then it can be found by username") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); // When repository.save(testUser); // Then auto foundUser = repository.findByUsername(Test::TEST_USERNAME_1); REQUIRE(foundUser.has_value()); Test::verifyDefaultTestUser(*foundUser); } SECTION("when multiple users are saved then findAll returns all users") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User firstUser = Test::createTestUser(); domain::User secondUser = Test::createSecondTestUser(); // When repository.save(firstUser); repository.save(secondUser); // Then auto allUsers = repository.findAll(); REQUIRE(allUsers.size() == 2); // Verify both users are present (order doesn't matter) bool foundFirst = false; bool foundSecond = false; for (const auto& user : allUsers) { if (user.id == Test::TEST_USER_ID_1) { Test::verifyDefaultTestUser(user); foundFirst = true; } else if (user.id == Test::TEST_USER_ID_2) { Test::verifySecondTestUser(user); foundSecond = true; } } REQUIRE(foundFirst); REQUIRE(foundSecond); } SECTION("when an existing user is saved then it is updated") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); repository.save(testUser); // When testUser.username = "updatedusername"; testUser.passwordHash = "updatedpasswordhash"; repository.save(testUser); // Then auto foundUser = repository.findById(Test::TEST_USER_ID_1); REQUIRE(foundUser.has_value()); REQUIRE(foundUser->id == Test::TEST_USER_ID_1); REQUIRE(foundUser->username == "updatedusername"); REQUIRE(foundUser->passwordHash == "updatedpasswordhash"); } SECTION("when a user is removed then it cannot be found by id") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); repository.save(testUser); // When repository.remove(Test::TEST_USER_ID_1); // Then auto foundUser = repository.findById(Test::TEST_USER_ID_1); REQUIRE_FALSE(foundUser.has_value()); } SECTION("when a user is removed then it cannot be found by username") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); repository.save(testUser); // When repository.remove(Test::TEST_USER_ID_1); // Then auto foundUser = repository.findByUsername(Test::TEST_USERNAME_1); REQUIRE_FALSE(foundUser.has_value()); } SECTION("when a user is removed then it is not in findAll") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User firstUser = Test::createTestUser(); domain::User secondUser = Test::createSecondTestUser(); repository.save(firstUser); repository.save(secondUser); // When repository.remove(Test::TEST_USER_ID_1); // Then auto allUsers = repository.findAll(); REQUIRE(allUsers.size() == 1); Test::verifySecondTestUser(allUsers[0]); } SECTION( "when findById is called with non-existent id then it returns nullopt") { // Given infrastructure::FileUserRepository repository(testDbPath); // When auto foundUser = repository.findById(Test::NON_EXISTENT_ID); // Then REQUIRE_FALSE(foundUser.has_value()); } SECTION("when findByUsername is called with non-existent username then it " "returns nullopt") { // Given infrastructure::FileUserRepository repository(testDbPath); // When auto foundUser = repository.findByUsername(Test::NON_EXISTENT_USERNAME); // Then REQUIRE_FALSE(foundUser.has_value()); } SECTION("when remove is called with non-existent id then it does nothing") { // Given infrastructure::FileUserRepository repository(testDbPath); domain::User testUser = Test::createTestUser(); repository.save(testUser); // When repository.remove(Test::NON_EXISTENT_ID); // Then auto allUsers = repository.findAll(); REQUIRE(allUsers.size() == 1); Test::verifyDefaultTestUser(allUsers[0]); } SECTION( "when repository is created with existing data file then it loads the data") { // Given { infrastructure::FileUserRepository firstRepository(testDbPath); domain::User testUser = Test::createTestUser(); firstRepository.save(testUser); } // When infrastructure::FileUserRepository secondRepository(testDbPath); // Then auto foundUser = secondRepository.findById(Test::TEST_USER_ID_1); REQUIRE(foundUser.has_value()); Test::verifyDefaultTestUser(*foundUser); } SECTION("when repository is created with non-existent data file then it " "starts empty") { // Given std::filesystem::path testDir = std::filesystem::temp_directory_path() / Test::TEST_DIR_NAME; std::string nonExistentDbPath = (testDir / "nonexistent.json").string(); // When infrastructure::FileUserRepository repository(nonExistentDbPath); // Then auto allUsers = repository.findAll(); REQUIRE(allUsers.empty()); } // Clean up test environment Test::cleanupTestEnvironment(); }