9 changed files with 1377 additions and 81 deletions
@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.20) |
||||
|
||||
enable_testing() |
||||
|
||||
find_package(Catch2 CONFIG REQUIRED) |
||||
|
||||
# Macro to create a test executable |
||||
function(add_integration_test TEST_NAME SOURCE_FILE) |
||||
add_executable(${TEST_NAME} |
||||
${SOURCE_FILE} |
||||
) |
||||
|
||||
target_link_libraries(${TEST_NAME} |
||||
PRIVATE |
||||
AutoStoreLib |
||||
Catch2::Catch2WithMain |
||||
) |
||||
|
||||
target_include_directories(${TEST_NAME} |
||||
PRIVATE |
||||
${PROJECT_SOURCE_DIR}/lib/include |
||||
) |
||||
|
||||
# Add test to CTest |
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) |
||||
endfunction() |
||||
|
||||
# Create test executables |
||||
add_integration_test(FileUserRepositoryTest integration/FileUserRepository.test.cpp) |
||||
add_integration_test(FileItemRepositoryTest integration/FileItemRepository.test.cpp) |
||||
@ -0,0 +1,365 @@
|
||||
#include <catch2/catch_test_macros.hpp> |
||||
#include <catch2/matchers/catch_matchers_string.hpp> |
||||
#include <autostore/infrastructure/repositories/FileItemRepository.h> |
||||
#include <autostore/domain/entities/Item.h> |
||||
#include <filesystem> |
||||
#include <fstream> |
||||
#include <optional> |
||||
#include <chrono> |
||||
|
||||
using namespace nxl::autostore; |
||||
using Catch::Matchers::Equals; |
||||
|
||||
namespace Test { |
||||
constexpr const char* TEST_ITEM_ID_1 = "item123"; |
||||
constexpr const char* TEST_ITEM_ID_2 = "item456"; |
||||
constexpr const char* TEST_ITEM_NAME_1 = "testitem"; |
||||
constexpr const char* TEST_ITEM_NAME_2 = "anotheritem"; |
||||
constexpr const char* TEST_ORDER_URL_1 = "https://example.com/order1"; |
||||
constexpr const char* TEST_ORDER_URL_2 = "https://example.com/order2"; |
||||
constexpr const char* TEST_USER_ID_1 = "user123"; |
||||
constexpr const char* TEST_USER_ID_2 = "user456"; |
||||
constexpr const char* NON_EXISTENT_ID = "nonexistent"; |
||||
constexpr const char* NON_EXISTENT_USER_ID = "nonexistentuser"; |
||||
constexpr const char* TEST_DIR_NAME = "autostore_test"; |
||||
constexpr const char* TEST_DB_FILE_NAME = "test_items.json"; |
||||
|
||||
// Helper function to create a test item with default values
|
||||
domain::Item |
||||
createTestItem(const std::string& id = TEST_ITEM_ID_1, |
||||
const std::string& name = TEST_ITEM_NAME_1, |
||||
const std::string& orderUrl = TEST_ORDER_URL_1, |
||||
const std::string& userId = TEST_USER_ID_1, |
||||
const std::chrono::system_clock::time_point& expirationDate = |
||||
std::chrono::system_clock::now() + std::chrono::hours(24)) |
||||
{ |
||||
domain::Item item; |
||||
item.id = id; |
||||
item.name = name; |
||||
item.orderUrl = orderUrl; |
||||
item.userId = userId; |
||||
item.expirationDate = expirationDate; |
||||
return item; |
||||
} |
||||
|
||||
// Helper function to create a second test item
|
||||
domain::Item createSecondTestItem() |
||||
{ |
||||
return createTestItem( |
||||
TEST_ITEM_ID_2, TEST_ITEM_NAME_2, TEST_ORDER_URL_2, TEST_USER_ID_2, |
||||
std::chrono::system_clock::now() + std::chrono::hours(48)); |
||||
} |
||||
|
||||
// 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 item properties match expected values
|
||||
void verifyItemProperties(const domain::Item& item, |
||||
const std::string& expectedId, |
||||
const std::string& expectedName, |
||||
const std::string& expectedOrderUrl, |
||||
const std::string& expectedUserId) |
||||
{ |
||||
REQUIRE(item.id == expectedId); |
||||
REQUIRE(item.name == expectedName); |
||||
REQUIRE(item.orderUrl == expectedOrderUrl); |
||||
REQUIRE(item.userId == expectedUserId); |
||||
} |
||||
|
||||
// Helper function to verify item properties match default test item values
|
||||
void verifyDefaultTestItem(const domain::Item& item) |
||||
{ |
||||
verifyItemProperties(item, TEST_ITEM_ID_1, TEST_ITEM_NAME_1, TEST_ORDER_URL_1, |
||||
TEST_USER_ID_1); |
||||
} |
||||
|
||||
// Helper function to verify item properties match second test item values
|
||||
void verifySecondTestItem(const domain::Item& item) |
||||
{ |
||||
verifyItemProperties(item, TEST_ITEM_ID_2, TEST_ITEM_NAME_2, TEST_ORDER_URL_2, |
||||
TEST_USER_ID_2); |
||||
} |
||||
} // namespace Test
|
||||
|
||||
TEST_CASE("FileItemRepository Integration Tests", |
||||
"[integration][FileItemRepository]") |
||||
{ |
||||
// Setup test environment
|
||||
std::string testDbPath = Test::setupTestEnvironment(); |
||||
|
||||
SECTION("when a new item is saved then it can be found by id") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
|
||||
// When
|
||||
repository.save(testItem); |
||||
|
||||
// Then
|
||||
auto foundItem = repository.findById(Test::TEST_ITEM_ID_1); |
||||
REQUIRE(foundItem.has_value()); |
||||
Test::verifyDefaultTestItem(*foundItem); |
||||
} |
||||
|
||||
SECTION("when a new item is saved then it can be found by user") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
|
||||
// When
|
||||
repository.save(testItem); |
||||
|
||||
// Then
|
||||
auto userItems = repository.findByUser(Test::TEST_USER_ID_1); |
||||
REQUIRE(userItems.size() == 1); |
||||
Test::verifyDefaultTestItem(userItems[0]); |
||||
} |
||||
|
||||
SECTION("when multiple items are saved then findAll returns all items") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item firstItem = Test::createTestItem(); |
||||
domain::Item secondItem = Test::createSecondTestItem(); |
||||
|
||||
// When
|
||||
repository.save(firstItem); |
||||
repository.save(secondItem); |
||||
|
||||
// Then
|
||||
auto allItems = repository.findAll(); |
||||
REQUIRE(allItems.size() == 2); |
||||
|
||||
// Verify both items are present (order doesn't matter)
|
||||
bool foundFirst = false; |
||||
bool foundSecond = false; |
||||
|
||||
for (const auto& item : allItems) { |
||||
if (item.id == Test::TEST_ITEM_ID_1) { |
||||
Test::verifyDefaultTestItem(item); |
||||
foundFirst = true; |
||||
} else if (item.id == Test::TEST_ITEM_ID_2) { |
||||
Test::verifySecondTestItem(item); |
||||
foundSecond = true; |
||||
} |
||||
} |
||||
|
||||
REQUIRE(foundFirst); |
||||
REQUIRE(foundSecond); |
||||
} |
||||
|
||||
SECTION("when multiple items for same user are saved then findByUser returns " |
||||
"all user items") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item firstItem = Test::createTestItem(); |
||||
domain::Item secondItem = |
||||
Test::createTestItem("item789", "thirditem", "https://example.com/order3", |
||||
Test::TEST_USER_ID_1); |
||||
|
||||
// When
|
||||
repository.save(firstItem); |
||||
repository.save(secondItem); |
||||
|
||||
// Then
|
||||
auto userItems = repository.findByUser(Test::TEST_USER_ID_1); |
||||
REQUIRE(userItems.size() == 2); |
||||
|
||||
// Verify both items are present (order doesn't matter)
|
||||
bool foundFirst = false; |
||||
bool foundSecond = false; |
||||
|
||||
for (const auto& item : userItems) { |
||||
if (item.id == Test::TEST_ITEM_ID_1) { |
||||
Test::verifyDefaultTestItem(item); |
||||
foundFirst = true; |
||||
} else if (item.id == "item789") { |
||||
REQUIRE(item.name == "thirditem"); |
||||
REQUIRE(item.orderUrl == "https://example.com/order3"); |
||||
REQUIRE(item.userId == Test::TEST_USER_ID_1); |
||||
foundSecond = true; |
||||
} |
||||
} |
||||
|
||||
REQUIRE(foundFirst); |
||||
REQUIRE(foundSecond); |
||||
} |
||||
|
||||
SECTION("when an existing item is saved then it is updated") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
repository.save(testItem); |
||||
|
||||
// When
|
||||
testItem.name = "updateditemname"; |
||||
testItem.orderUrl = "https://updated.example.com/order"; |
||||
testItem.userId = Test::TEST_USER_ID_2; |
||||
repository.save(testItem); |
||||
|
||||
// Then
|
||||
auto foundItem = repository.findById(Test::TEST_ITEM_ID_1); |
||||
REQUIRE(foundItem.has_value()); |
||||
REQUIRE(foundItem->id == Test::TEST_ITEM_ID_1); |
||||
REQUIRE(foundItem->name == "updateditemname"); |
||||
REQUIRE(foundItem->orderUrl == "https://updated.example.com/order"); |
||||
REQUIRE(foundItem->userId == Test::TEST_USER_ID_2); |
||||
} |
||||
|
||||
SECTION("when an item is removed then it cannot be found by id") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
repository.save(testItem); |
||||
|
||||
// When
|
||||
repository.remove(Test::TEST_ITEM_ID_1); |
||||
|
||||
// Then
|
||||
auto foundItem = repository.findById(Test::TEST_ITEM_ID_1); |
||||
REQUIRE_FALSE(foundItem.has_value()); |
||||
} |
||||
|
||||
SECTION("when an item is removed then it is not in findByUser") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
repository.save(testItem); |
||||
|
||||
// When
|
||||
repository.remove(Test::TEST_ITEM_ID_1); |
||||
|
||||
// Then
|
||||
auto userItems = repository.findByUser(Test::TEST_USER_ID_1); |
||||
REQUIRE(userItems.empty()); |
||||
} |
||||
|
||||
SECTION("when an item is removed then it is not in findAll") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item firstItem = Test::createTestItem(); |
||||
domain::Item secondItem = Test::createSecondTestItem(); |
||||
repository.save(firstItem); |
||||
repository.save(secondItem); |
||||
|
||||
// When
|
||||
repository.remove(Test::TEST_ITEM_ID_1); |
||||
|
||||
// Then
|
||||
auto allItems = repository.findAll(); |
||||
REQUIRE(allItems.size() == 1); |
||||
Test::verifySecondTestItem(allItems[0]); |
||||
} |
||||
|
||||
SECTION( |
||||
"when findById is called with non-existent id then it returns nullopt") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
|
||||
// When
|
||||
auto foundItem = repository.findById(Test::NON_EXISTENT_ID); |
||||
|
||||
// Then
|
||||
REQUIRE_FALSE(foundItem.has_value()); |
||||
} |
||||
|
||||
SECTION("when findByUser is called with non-existent user id then it returns " |
||||
"empty vector") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
repository.save(testItem); |
||||
|
||||
// When
|
||||
auto userItems = repository.findByUser(Test::NON_EXISTENT_USER_ID); |
||||
|
||||
// Then
|
||||
REQUIRE(userItems.empty()); |
||||
} |
||||
|
||||
SECTION("when remove is called with non-existent id then it does nothing") |
||||
{ |
||||
// Given
|
||||
infrastructure::FileItemRepository repository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
repository.save(testItem); |
||||
|
||||
// When
|
||||
repository.remove(Test::NON_EXISTENT_ID); |
||||
|
||||
// Then
|
||||
auto allItems = repository.findAll(); |
||||
REQUIRE(allItems.size() == 1); |
||||
Test::verifyDefaultTestItem(allItems[0]); |
||||
} |
||||
|
||||
SECTION( |
||||
"when repository is created with existing data file then it loads the data") |
||||
{ |
||||
// Given
|
||||
{ |
||||
infrastructure::FileItemRepository firstRepository(testDbPath); |
||||
domain::Item testItem = Test::createTestItem(); |
||||
firstRepository.save(testItem); |
||||
} |
||||
|
||||
// When
|
||||
infrastructure::FileItemRepository secondRepository(testDbPath); |
||||
|
||||
// Then
|
||||
auto foundItem = secondRepository.findById(Test::TEST_ITEM_ID_1); |
||||
REQUIRE(foundItem.has_value()); |
||||
Test::verifyDefaultTestItem(*foundItem); |
||||
} |
||||
|
||||
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::FileItemRepository repository(nonExistentDbPath); |
||||
|
||||
// Then
|
||||
auto allItems = repository.findAll(); |
||||
REQUIRE(allItems.empty()); |
||||
} |
||||
|
||||
// Clean up test environment
|
||||
Test::cleanupTestEnvironment(); |
||||
} |
||||
@ -0,0 +1,312 @@
|
||||
#include <catch2/catch_test_macros.hpp> |
||||
#include <catch2/matchers/catch_matchers_string.hpp> |
||||
#include <autostore/infrastructure/repositories/FileUserRepository.h> |
||||
#include <autostore/domain/entities/User.h> |
||||
#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(); |
||||
} |
||||
@ -0,0 +1,564 @@
|
||||
openapi: 3.0.3 |
||||
info: |
||||
title: AutoStore API |
||||
description: API for the AutoStore system - a system to store items with expiration dates that automatically orders new items when they expire. |
||||
version: 1.0.0 |
||||
servers: |
||||
- url: http://localhost:3000/api/v1 |
||||
description: Development server |
||||
paths: |
||||
/register: |
||||
post: |
||||
summary: Register a new user |
||||
description: Creates a new user account and returns a JWT token |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
type: object |
||||
required: |
||||
- username |
||||
- password |
||||
properties: |
||||
username: |
||||
type: string |
||||
description: User's username or email |
||||
password: |
||||
type: string |
||||
description: User's password |
||||
responses: |
||||
'201': |
||||
description: User successfully registered |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
type: object |
||||
properties: |
||||
user: |
||||
$ref: '#/components/schemas/User' |
||||
token: |
||||
type: string |
||||
description: JWT token for authentication |
||||
'400': |
||||
description: Invalid input |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'409': |
||||
description: Username already exists |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
/login: |
||||
post: |
||||
summary: User login |
||||
description: Authenticates a user and returns a JWT token |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
type: object |
||||
required: |
||||
- username |
||||
- password |
||||
properties: |
||||
username: |
||||
type: string |
||||
description: User's username or email |
||||
password: |
||||
type: string |
||||
description: User's password |
||||
responses: |
||||
'200': |
||||
description: Login successful |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
type: object |
||||
properties: |
||||
user: |
||||
$ref: '#/components/schemas/User' |
||||
token: |
||||
type: string |
||||
description: JWT token for authentication |
||||
'401': |
||||
description: Invalid credentials |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
/users: |
||||
get: |
||||
summary: Get list of users |
||||
description: Returns a list of all users (requires authentication) |
||||
security: |
||||
- bearerAuth: [] |
||||
responses: |
||||
'200': |
||||
description: List of users |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
type: array |
||||
items: |
||||
$ref: '#/components/schemas/User' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
/users/{id}: |
||||
get: |
||||
summary: Get user by ID |
||||
description: Returns a specific user by their ID (requires authentication) |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: User ID |
||||
schema: |
||||
type: string |
||||
responses: |
||||
'200': |
||||
description: User details |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/User' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: User not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
post: |
||||
summary: Create a new user |
||||
description: Creates a new user (admin functionality, requires authentication) |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: User ID |
||||
schema: |
||||
type: string |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/UserInput' |
||||
responses: |
||||
'201': |
||||
description: User created successfully |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/User' |
||||
'400': |
||||
description: Invalid input |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'409': |
||||
description: User already exists |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
put: |
||||
summary: Update a user |
||||
description: Updates an existing user (requires authentication) |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: User ID |
||||
schema: |
||||
type: string |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/UserInput' |
||||
responses: |
||||
'200': |
||||
description: User updated successfully |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/User' |
||||
'400': |
||||
description: Invalid input |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: User not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
delete: |
||||
summary: Delete a user |
||||
description: Deletes an existing user (requires authentication) |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: User ID |
||||
schema: |
||||
type: string |
||||
responses: |
||||
'204': |
||||
description: User deleted successfully |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: User not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
/items: |
||||
get: |
||||
summary: Get list of items |
||||
description: Returns a list of all items for the authenticated user |
||||
security: |
||||
- bearerAuth: [] |
||||
responses: |
||||
'200': |
||||
description: List of items |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
type: array |
||||
items: |
||||
$ref: '#/components/schemas/Item' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
post: |
||||
summary: Create a new item |
||||
description: Creates a new item for the authenticated user |
||||
security: |
||||
- bearerAuth: [] |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/ItemInput' |
||||
responses: |
||||
'201': |
||||
description: Item created successfully |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/Item' |
||||
'400': |
||||
description: Invalid input |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
/items/{id}: |
||||
get: |
||||
summary: Get item by ID |
||||
description: Returns a specific item by its ID |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: Item ID |
||||
schema: |
||||
type: string |
||||
responses: |
||||
'200': |
||||
description: Item details |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/Item' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: Item not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
put: |
||||
summary: Update an item |
||||
description: Updates an existing item |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: Item ID |
||||
schema: |
||||
type: string |
||||
requestBody: |
||||
required: true |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/ItemInput' |
||||
responses: |
||||
'200': |
||||
description: Item updated successfully |
||||
content: |
||||
application/json: |
||||
schema: |
||||
allOf: |
||||
- $ref: '#/components/schemas/JsendSuccess' |
||||
- type: object |
||||
properties: |
||||
data: |
||||
$ref: '#/components/schemas/Item' |
||||
'400': |
||||
description: Invalid input |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: Item not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
delete: |
||||
summary: Delete an item |
||||
description: Deletes an existing item |
||||
security: |
||||
- bearerAuth: [] |
||||
parameters: |
||||
- name: id |
||||
in: path |
||||
required: true |
||||
description: Item ID |
||||
schema: |
||||
type: string |
||||
responses: |
||||
'204': |
||||
description: Item deleted successfully |
||||
'401': |
||||
description: Unauthorized |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
'404': |
||||
description: Item not found |
||||
content: |
||||
application/json: |
||||
schema: |
||||
$ref: '#/components/schemas/JsendError' |
||||
|
||||
components: |
||||
securitySchemes: |
||||
bearerAuth: |
||||
type: http |
||||
scheme: bearer |
||||
bearerFormat: JWT |
||||
|
||||
schemas: |
||||
JsendSuccess: |
||||
type: object |
||||
properties: |
||||
status: |
||||
type: string |
||||
example: success |
||||
data: |
||||
type: object |
||||
description: Response data |
||||
|
||||
JsendError: |
||||
type: object |
||||
properties: |
||||
status: |
||||
type: string |
||||
example: error |
||||
message: |
||||
type: string |
||||
description: Error message |
||||
code: |
||||
type: integer |
||||
description: Error code |
||||
data: |
||||
type: object |
||||
description: Additional error data |
||||
|
||||
User: |
||||
type: object |
||||
properties: |
||||
id: |
||||
type: string |
||||
description: User ID |
||||
username: |
||||
type: string |
||||
description: User's username or email |
||||
|
||||
UserInput: |
||||
type: object |
||||
required: |
||||
- username |
||||
- password |
||||
properties: |
||||
username: |
||||
type: string |
||||
description: User's username or email |
||||
password: |
||||
type: string |
||||
description: User's password |
||||
|
||||
Item: |
||||
type: object |
||||
properties: |
||||
id: |
||||
type: string |
||||
description: Item ID |
||||
name: |
||||
type: string |
||||
description: Item name |
||||
expirationDate: |
||||
type: string |
||||
format: date-time |
||||
description: Item expiration date |
||||
orderUrl: |
||||
type: string |
||||
format: uri |
||||
description: URL to send order request when item expires |
||||
userId: |
||||
type: string |
||||
description: ID of the user who owns this item |
||||
|
||||
ItemInput: |
||||
type: object |
||||
required: |
||||
- name |
||||
- expirationDate |
||||
- orderUrl |
||||
properties: |
||||
name: |
||||
type: string |
||||
description: Item name |
||||
expirationDate: |
||||
type: string |
||||
format: date-time |
||||
description: Item expiration date |
||||
orderUrl: |
||||
type: string |
||||
format: uri |
||||
description: URL to send order request when item expires |
||||
Loading…
Reference in new issue