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