You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
9.0 KiB
312 lines
9.0 KiB
#include "infrastructure/repositories/FileUserRepository.h" |
|
#include "domain/entities/User.h" |
|
#include <catch2/catch_test_macros.hpp> |
|
#include <catch2/matchers/catch_matchers_string.hpp> |
|
#include <filesystem> |
|
#include <fstream> |
|
#include <optional> |
|
|
|
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(); |
|
} |