diff --git a/src/application/services/folder_service.cpp b/src/application/services/folder_service.cpp index dcd37477b..fa6137738 100644 --- a/src/application/services/folder_service.cpp +++ b/src/application/services/folder_service.cpp @@ -39,30 +39,42 @@ Folder* FolderService::getFolder(const QUuid& uuid) return getFolderHelper(uuid, m_rootFolder.get()); } +Folder* FolderService::getFolderHelper(const QUuid& uuid, Folder* curr) +{ + if(curr->getUuid() == uuid) + return curr; + + for(auto& child : curr->getChildren()) + { + auto result = getFolderHelper(uuid, child.get()); + if(result != nullptr) + return result; + } + + return nullptr; +} + bool FolderService::createFolder(const QString& name, QString color, QString icon, QString description, const QUuid& parent) { Folder* parentFolder = nullptr; - // This means that the folder should be created in the root folder. - if(parent.isNull()) + if(parent.isNull()) // Set the root folder as the parent parentFolder = m_rootFolder.get(); else parentFolder = getFolder(parent); if(parentFolder == nullptr) + { + qWarning() << "Creating folder failed. Could not find parent folder " + "with uuid: " + << parent; return false; + } - // Set the index in parent to the current child count, since we're adding - // the folder to the end of the parent's children. - auto child = std::make_unique(name, color, icon, description); - child->setIndexInParent(parentFolder->childCount()); - - emit beginInsertFolder(parentFolder, parentFolder->childCount()); - parentFolder->addChild(std::move(child)); - emit endInsertFolder(); + auto newFolder = std::make_unique(name, color, icon, description); + createFolderHelper(std::move(newFolder), parentFolder); - parentFolder->updateLastModified(); saveChanges(); return true; } @@ -71,22 +83,20 @@ QList FolderService::deleteFolder(const QUuid& uuid) { auto folder = getFolder(uuid); if(folder == nullptr) + { + qWarning() + << "Deleting folder failed. Could not find folder with uuid: " + << uuid; return {}; + } - auto parent = folder->getParent(); - auto indexInParent = folder->getIndexInParent(); - - // We return a list of all the folders that are deleted (since all - // descendents of the folder are also deleted). + // We return a list of all the folders that are deleted (all + // descendents of the folder are deleted as well). auto listOfDescendents = getUuidsOfAllDescendents(*folder); listOfDescendents.push_front(uuid.toString(QUuid::WithoutBraces)); - emit beginRemoveFolder(parent, indexInParent); - parent->removeChild(uuid); - emit endRemoveFolder(); + deleteFolderHelper(folder); - parent->decreaseChildIndiciesIfBiggerThan(indexInParent); - parent->updateLastModified(); saveChanges(); return listOfDescendents; } @@ -94,6 +104,14 @@ QList FolderService::deleteFolder(const QUuid& uuid) void FolderService::updateFolder(const domain::entities::Folder& folder) { auto realFolder = getFolder(folder.getUuid()); + if(realFolder == nullptr) + { + qWarning() + << "Updating folder failed. Could not find folder with uuid: " + << folder.getUuid(); + return; + } + realFolder->setName(folder.getName()); realFolder->setColor(folder.getColor()); realFolder->setIcon(folder.getIcon()); @@ -108,6 +126,12 @@ bool FolderService::moveFolder(const QUuid& uuid, const QUuid& destUuid) { auto currFolder = getFolder(uuid); auto destFolder = getFolder(destUuid); + if(currFolder == nullptr || destFolder == nullptr) + { + qWarning() << "Folder move operation failed. Could not find source or " + "destination"; + return false; + } // We don't need to do anything if dest already is the parent. if(currFolder->getParent()->getUuid() == destFolder->getUuid()) @@ -121,27 +145,12 @@ bool FolderService::moveFolder(const QUuid& uuid, const QUuid& destUuid) } // Make copy since we delete the currFolder from its parent which frees - // it's memory. + // the original folder's memory. auto currFolderCopy = std::make_unique(*currFolder); - auto currentParent = currFolder->getParent(); - // Since we remove the child from it's old parent, all other children's - // indecies need to be adjusted. - currentParent->decreaseChildIndiciesIfBiggerThan( - currFolderCopy->getIndexInParent()); + deleteFolderHelper(currFolder); + createFolderHelper(std::move(currFolderCopy), destFolder); - emit beginRemoveFolder(currFolder->getParent(), - currFolder->getIndexInParent()); - currentParent->removeChild(uuid); - currentParent->updateLastModified(); - emit endRemoveFolder(); - - currFolderCopy->setIndexInParent(destFolder->childCount()); - emit beginInsertFolder(destFolder, destFolder->childCount()); - destFolder->addChild(std::move(currFolderCopy)); - emit endInsertFolder(); - - destFolder->updateLastModified(); saveChanges(); return true; } @@ -150,59 +159,54 @@ void FolderService::setupUserData(const QString& token, const QString& email) { m_authenticationToken = token; m_localLibraryTracker->setLibraryOwner(email); + m_rootFolder->getChildren().clear(); auto folder = m_localLibraryTracker->loadFolders(); - - // This occurs when the user has no library yet. If this occurs, we want to - // create a default folder under the root folder for them. - if(folder.getName() == "invalid") - { - auto defaultFolder = std::make_unique( - "Archive", "default", "folder", "A folder for archived books"); - defaultFolder->setIndexInParent(0); - - m_rootFolder->addChild(std::move(defaultFolder)); - } + if(folder.getName() == "invalid") // No local library exists yet + createDefaultFolder(); else - { - // Update the root folder's uuid with the stored uuid - m_rootFolder->setUuid(folder.getUuid()); - m_rootFolder->setLastModified(folder.getLastModified()); - - // We need to add the folder's children to the current root element, - // since a pointer of the root element was already passed to the model, - // so we can't simply overwrite it, else we'd invalidate the pointer. - for(auto& child : folder.getChildren()) - m_rootFolder->addChild(std::move(child)); - - // JSON format does not guarantee that the children are sorted. - m_rootFolder->sortDescendents(); - } + overwriteRootFolderWith(folder); // Trigger a timeout manually at the start for the initial folder loading m_fetchChangesTimer.start(); QMetaObject::invokeMethod(&m_fetchChangesTimer, "timeout"); } -void FolderService::clearUserData() +void FolderService::createDefaultFolder() { - m_localLibraryTracker->clearLibraryOwner(); + auto defaultFolder = std::make_unique( + "Archive", "default", "folder", "A folder for archived books"); + defaultFolder->setIndexInParent(0); + + m_rootFolder->addChild(std::move(defaultFolder)); } -Folder* FolderService::getFolderHelper(const QUuid& uuid, - domain::entities::Folder* parent) +void FolderService::overwriteRootFolderWith(Folder& folder) { - if(parent->getUuid() == uuid) - return parent; + m_rootFolder->setUuid(folder.getUuid()); + m_rootFolder->setLastModified(folder.getLastModified()); + m_rootFolder->getChildren().clear(); - for(auto& child : parent->getChildren()) - { - auto result = getFolderHelper(uuid, child.get()); - if(result != nullptr) - return result; - } + // We need to add the folder's children to the current root element, + // since a pointer of the root element was already passed to the folders + // model, so we can't simply overwrite it, else we'd invalidate the pointer. + for(auto& child : folder.getChildren()) + m_rootFolder->addChild(std::move(child)); - return nullptr; + // JSON format does not guarantee that the children are in the correct order + // when loaded, so we need to sort them. + m_rootFolder->sortDescendents(); +} + +void FolderService::clearUserData() +{ + m_localLibraryTracker->clearLibraryOwner(); + + m_rootFolder->getChildren().clear(); + m_rootFolder->setUuid(QUuid()); + m_rootFolder->setLastModified(QDateTime::currentDateTime()); + + m_fetchChangesTimer.stop(); } void FolderService::saveChanges() @@ -213,103 +217,75 @@ void FolderService::saveChanges() void FolderService::processFetchedFolders(Folder& remoteRoot) { - // This occurs when there is no folder on the server yet. We want to create - // a default folder for the user. - if(remoteRoot.getName().isEmpty()) + // This occurs when there is no folder on the server yet. If that's the + // case, we just want to sync whatever we have locally to the server. + if(remoteRoot.getName() == "") { m_folderStorageGateway->updateFolder(m_authenticationToken, *m_rootFolder); return; } - // This occurs when there is folder data on the server, but there is no - // local folder data on the client yet. E.g. when starting the application - // for the first time from a new machine, while folders already exist on the - // server. - // In this case we want to just take all of the data from the server by - // setting the rootFolder's uuid, removing the child created by default and - // assigning the server folder's children to the root folder. + // This occurs when there is folder data on the server, but it wasn never + // synced with the client (thus the root uuids are different). In this case, + // we just want to overwrite the local folders with the remote folders. auto localRoot = getFolder(remoteRoot.getUuid()); if(localRoot == nullptr) { - m_rootFolder->setUuid(remoteRoot.getUuid()); - m_rootFolder->removeChild(m_rootFolder->getChildAtIndex(0)->getUuid()); - emit beginModelReset(); - for(auto& child : remoteRoot.getChildren()) - m_rootFolder->addChild(std::move(child)); - - m_rootFolder->sortDescendents(); + overwriteRootFolderWith(remoteRoot); emit endModelReset(); } - // We don't need update the local folders if we copy over everything from - // the server since local and server will be exactly the same. else { + emit beginModelReset(); updateFoldersRecursively(localRoot, remoteRoot); m_rootFolder->sortDescendents(); + emit endModelReset(); } saveChanges(); } -void FolderService::updateFoldersRecursively(Folder* current, - Folder& remoteRoot) +void FolderService::updateFoldersRecursively(Folder* curr, Folder& remoteRoot) { Folder* remoteFolder = nullptr; - if(current == m_rootFolder.get()) + if(curr == m_rootFolder.get()) remoteFolder = &remoteRoot; else - remoteFolder = remoteRoot.getDescendant(current->getUuid()); + remoteFolder = remoteRoot.getDescendant(curr->getUuid()); - // This occurs when the remote folder was added, and thus moved from, during - // the 'addMissingChildrenToFolder' step. We just skip it. + // This occurs when the remote folder was added locally, and thus moved + // from. We just skip it since it was already dealt with. if(remoteFolder == nullptr) return; - auto currLastModified = current->getLastModified().toSecsSinceEpoch(); + auto currLastModified = curr->getLastModified().toSecsSinceEpoch(); auto remoteLastModified = remoteFolder->getLastModified().toSecsSinceEpoch(); if(remoteLastModified > currLastModified) { - current->updateProperties(*remoteFolder); + curr->updateProperties(*remoteFolder); // We don't want to emit refreshFolder for the root folder - if(current->getName() != "ROOT") - { - emit refreshFolder(current->getParent(), - current->getIndexInParent()); - } + if(curr->getName() != "ROOT") + emit refreshFolder(curr->getParent(), curr->getIndexInParent()); // If the remote folder has changed, we treat it as the source of // truth and replace the whole subtree of the current folder with the // remote subtree. - QList foldersToRemove; - for(auto& child : current->getChildren()) - foldersToRemove.push_back(child->getUuid()); - - for(auto& uuid : foldersToRemove) - { - emit beginRemoveFolder(current, current->getIndexOfChild(uuid)); - current->removeChild(uuid); - emit endRemoveFolder(); - } - + curr->getChildren().clear(); for(auto& child : remoteFolder->getChildren()) { - child->setParent(current); - emit beginInsertFolder(current, current->childCount()); - current->addChild(std::move(child)); - emit endInsertFolder(); + child->setIndexInParent(curr->childCount()); + curr->addChild(std::make_unique(*child)); } return; } - for(auto& child : current->getChildren()) - { + for(auto& child : curr->getChildren()) updateFoldersRecursively(child.get(), remoteRoot); - } } QList FolderService::getUuidsOfAllDescendents(const Folder& folder) @@ -325,4 +301,32 @@ QList FolderService::getUuidsOfAllDescendents(const Folder& folder) return uuids; } +void FolderService::createFolderHelper(std::unique_ptr folder, + Folder* parent) +{ + folder->setIndexInParent(parent->childCount()); + + emit beginInsertFolder(parent, parent->childCount()); + parent->addChild(std::move(folder)); + emit endInsertFolder(); + + parent->updateLastModified(); +} + +void FolderService::deleteFolderHelper(Folder* folder) +{ + int indexInParent = folder->getIndexInParent(); + auto parent = folder->getParent(); + + emit beginRemoveFolder(folder->getParent(), indexInParent); + auto success = parent->removeChild(folder->getUuid()); + emit endRemoveFolder(); + + if(success) + { + parent->decreaseChildIndiciesIfBiggerThan(indexInParent); + parent->updateLastModified(); + } +} + } // namespace application::services \ No newline at end of file diff --git a/src/application/services/folder_service.hpp b/src/application/services/folder_service.hpp index 7752552a7..d1d0b2816 100644 --- a/src/application/services/folder_service.hpp +++ b/src/application/services/folder_service.hpp @@ -41,6 +41,11 @@ private slots: domain::entities::Folder& remoteFolder); QList getUuidsOfAllDescendents( const domain::entities::Folder& folder); + void createFolderHelper(std::unique_ptr folder, + domain::entities::Folder* parent); + void deleteFolderHelper(domain::entities::Folder* folder); + void createDefaultFolder(); + void overwriteRootFolderWith(domain::entities::Folder& folder); IFolderStorageGateway* m_folderStorageGateway; ILocalLibraryTracker* m_localLibraryTracker; diff --git a/src/domain/entities/folder.cpp b/src/domain/entities/folder.cpp index 277ab2d87..7dda83d66 100644 --- a/src/domain/entities/folder.cpp +++ b/src/domain/entities/folder.cpp @@ -307,7 +307,7 @@ void Folder::addChild(std::unique_ptr child) m_children.emplace_back(std::move(child)); } -void Folder::removeChild(const QUuid& uuid) +bool Folder::removeChild(const QUuid& uuid) { auto it = std::find_if(m_children.begin(), m_children.end(), [uuid](const auto& child) @@ -318,7 +318,10 @@ void Folder::removeChild(const QUuid& uuid) if(it != m_children.end()) { m_children.erase(it); + return true; } + + return false; } const Folder* Folder::getChildAtIndex(int index) const diff --git a/src/domain/entities/folder.hpp b/src/domain/entities/folder.hpp index 4b951ca9e..271df736a 100644 --- a/src/domain/entities/folder.hpp +++ b/src/domain/entities/folder.hpp @@ -62,7 +62,7 @@ class Folder Folder* getChild(const QUuid& uuid); Folder* getDescendant(const QUuid& uuid); void addChild(std::unique_ptr child); - void removeChild(const QUuid& uuid); + bool removeChild(const QUuid& uuid); const Folder* getChildAtIndex(int index) const; Folder* getChildAtIndex(int index); int childCount() const; diff --git a/tests/application_unit_tests/CMakeLists.txt b/tests/application_unit_tests/CMakeLists.txt index 1c184d958..ffd278dcd 100644 --- a/tests/application_unit_tests/CMakeLists.txt +++ b/tests/application_unit_tests/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(application_unit_tests services/user_service_tests.cpp services/settings_service_tests.cpp services/authentication_service_tests.cpp + services/folder_service_tests.cpp utility/local_library_tracker_tests.cpp utility/library_storage_manager_tests.cpp diff --git a/tests/application_unit_tests/services/folder_service_tests.cpp b/tests/application_unit_tests/services/folder_service_tests.cpp new file mode 100644 index 000000000..822ebe25f --- /dev/null +++ b/tests/application_unit_tests/services/folder_service_tests.cpp @@ -0,0 +1,421 @@ +#include +#include +#include +#include "folder.hpp" +#include "folder_service.hpp" +#include "i_folder_storage_gateway.hpp" + +using namespace testing; +using ::testing::ReturnRef; +using namespace application::services; +using namespace application; +using namespace domain::entities; + +namespace tests::application +{ + +class FolderStorageGatewayMock : public IFolderStorageGateway +{ +public: + MOCK_METHOD(void, updateFolder, (const QString&, const Folder&), + (override)); + MOCK_METHOD(void, fetchFolders, (const QString&), (override)); +}; + +class LocalLibraryTrackerMock : public ILocalLibraryTracker +{ +public: + MOCK_METHOD(void, setLibraryOwner, (const QString&), (override)); + MOCK_METHOD(void, clearLibraryOwner, (), (override)); + MOCK_METHOD(QDir, getLibraryDir, (), (const, override)); + MOCK_METHOD(std::vector, getTrackedBooks, (), (override)); + MOCK_METHOD(std::optional, getTrackedBook, (const QUuid&), + (override)); + MOCK_METHOD(bool, trackBook, (const Book& book), (override)); + MOCK_METHOD(bool, untrackBook, (const QUuid&), (override)); + MOCK_METHOD(bool, updateTrackedBook, (const Book&), (override)); + MOCK_METHOD(void, saveFolders, (const Folder&), (override)); + MOCK_METHOD(Folder, loadFolders, (), (override)); +}; + +struct AFolderService : public ::testing::Test +{ + void SetUp() override + { + folderService = std::make_unique( + &folderStorageGatewayMock, &localLibraryTrackerMock); + } + + FolderStorageGatewayMock folderStorageGatewayMock; + LocalLibraryTrackerMock localLibraryTrackerMock; + std::unique_ptr folderService; +}; + +TEST_F(AFolderService, SucceedsCreatingAFolderWithRootAsParent) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Act + auto success = folderService->createFolder("name", "color", "icon", + "description", QUuid()); + + // Assert + ASSERT_TRUE(success); + auto rootFolder = folderService->getRootFolder(); + ASSERT_EQ(rootFolder->getChildren().size(), 1); + ASSERT_EQ(rootFolder->getChildren().at(0)->getParent(), rootFolder); +} + +TEST_F(AFolderService, SucceedsCreatingAFolderWithExistingFolderAsParent) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(2); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(2); + + // Arrange + folderService->createFolder("name", "color", "icon", "description", + QUuid()); + auto parentFolder = folderService->getRootFolder()->getChildAtIndex(0); + + // Act + auto success = folderService->createFolder( + "second", "default", "folder", "description", parentFolder->getUuid()); + + // Assert + ASSERT_TRUE(success); + ASSERT_EQ(parentFolder->getChildren().size(), 1); + ASSERT_EQ(parentFolder->getChildren().at(0)->getParent(), parentFolder); +} + +TEST_F(AFolderService, FailsCreatingAFolderIfParentDoesNotExist) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(0); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(0); + + // Arrange + QUuid nonExistentParent = QUuid::createUuid(); + + // Act + auto success = folderService->createFolder( + "name", "color", "icon", "description", nonExistentParent); + + // Assert + ASSERT_FALSE(success); + auto rootFolder = folderService->getRootFolder(); + ASSERT_EQ(rootFolder->getChildren().size(), 0); +} + +TEST_F(AFolderService, SucceedsDeletingAFolder) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(4); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(4); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + folderService->createFolder("second", "color", "icon", "description", + QUuid()); + folderService->createFolder("third", "color", "icon", "description", + QUuid()); + + auto secondUuid = + folderService->getRootFolder()->getChildAtIndex(1)->getUuid(); + + // Act + auto deletedFolders = folderService->deleteFolder(secondUuid); + + // Assert + ASSERT_EQ(deletedFolders.size(), 1); + ASSERT_EQ(deletedFolders.at(0), secondUuid.toString(QUuid::WithoutBraces)); + + // Ensure that the indexInParent values were changed properly + auto thirdFolder = folderService->getRootFolder()->getChildAtIndex(1); + ASSERT_EQ(thirdFolder->getIndexInParent(), 1); +} + +TEST_F(AFolderService, SucceedsDeletingAFolderWithChildren) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)) + .Times(AnyNumber()); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(AnyNumber()); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + folderService->createFolder("second", "color", "icon", "description", + QUuid()); + + auto secondUuid = + folderService->getRootFolder()->getChildAtIndex(1)->getUuid(); + folderService->createFolder("child", "color", "icon", "description", + secondUuid); + folderService->createFolder("child2", "color", "icon", "description", + secondUuid); + + auto child2Uuid = folderService->getRootFolder() + ->getChildAtIndex(1) + ->getChildAtIndex(1) + ->getUuid(); + folderService->createFolder("grandChild", "color", "icon", "description", + child2Uuid); + + // Act + auto deletedFolders = folderService->deleteFolder(secondUuid); + + // Assert + ASSERT_EQ(deletedFolders.size(), 4); + ASSERT_TRUE( + deletedFolders.contains(secondUuid.toString(QUuid::WithoutBraces))); + ASSERT_TRUE( + deletedFolders.contains(child2Uuid.toString(QUuid::WithoutBraces))); +} + +TEST_F(AFolderService, FailsDeletingAFolderIfFolderDoesNotExist) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + + // Act + auto deletedFolders = folderService->deleteFolder(QUuid::createUuid()); + + // Assert + ASSERT_EQ(deletedFolders.size(), 0); +} + +TEST_F(AFolderService, SucceedsUpdatingAFolder) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(2); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(2); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + auto firstFolder = folderService->getRootFolder()->getChildAtIndex(0); + + // Act + Folder updateFolder = *firstFolder; + updateFolder.setName("newName"); + updateFolder.setColor("newColor"); + updateFolder.setIcon("newIcon"); + updateFolder.setDescription("newDescription"); + folderService->updateFolder(updateFolder); + + // Assert + ASSERT_EQ(firstFolder->getName(), updateFolder.getName()); + ASSERT_EQ(firstFolder->getColor(), updateFolder.getColor()); + ASSERT_EQ(firstFolder->getIcon(), updateFolder.getIcon()); + ASSERT_EQ(firstFolder->getDescription(), updateFolder.getDescription()); +} + +TEST_F(AFolderService, FailsUpdatingAFolderIfFolderDoesNotExist) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Arrange + QString originalName = "first"; + QString originalColor = "color"; + QString originalIcon = "icon"; + QString originalDescription = "description"; + folderService->createFolder(originalName, originalColor, originalIcon, + originalDescription, QUuid()); + auto firstFolder = folderService->getRootFolder()->getChildAtIndex(0); + + // Act + Folder updateFolder = *firstFolder; + updateFolder.setUuid(QUuid::createUuid()); // New Uuid + updateFolder.setName("newName"); + updateFolder.setColor("newColor"); + updateFolder.setIcon("newIcon"); + updateFolder.setDescription("newDescription"); + folderService->updateFolder(updateFolder); + + // Assert + ASSERT_EQ(firstFolder->getName(), originalName); + ASSERT_EQ(firstFolder->getColor(), originalColor); + ASSERT_EQ(firstFolder->getIcon(), originalIcon); + ASSERT_EQ(firstFolder->getDescription(), originalDescription); +} + +TEST_F(AFolderService, SucceedsMovingAFolder) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(4); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(4); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + folderService->createFolder("second", "color", "icon", "description", + QUuid()); + folderService->createFolder("third", "color", "icon", "description", + QUuid()); + auto firstFolderUuid = + folderService->getRootFolder()->getChildAtIndex(0)->getUuid(); + auto secondFolderUuid = + folderService->getRootFolder()->getChildAtIndex(1)->getUuid(); + auto thirdFolderUuid = + folderService->getRootFolder()->getChildAtIndex(2)->getUuid(); + + // Act + auto success = folderService->moveFolder(firstFolderUuid, secondFolderUuid); + + // Assert + auto firstFolder = folderService->getFolder(firstFolderUuid); + auto secondFolder = folderService->getFolder(secondFolderUuid); + auto thirdFolder = folderService->getFolder(thirdFolderUuid); + + ASSERT_TRUE(success); + ASSERT_EQ(folderService->getRootFolder()->childCount(), 2); + ASSERT_EQ(secondFolder->getChildren().size(), 1); + ASSERT_EQ(secondFolder->getChildAtIndex(0)->getUuid(), firstFolderUuid); + ASSERT_EQ(firstFolder->getParent()->getUuid(), secondFolderUuid); + + // Ensure that the indexInParent values were adjusted correctly + ASSERT_EQ(firstFolder->getIndexInParent(), 0); + ASSERT_EQ(secondFolder->getIndexInParent(), 0); + ASSERT_EQ(thirdFolder->getIndexInParent(), 1); +} + +TEST_F(AFolderService, FailsMovingAFolderIfFolderDoesNotExist) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + auto firstFolderUuid = + folderService->getRootFolder()->getChildAtIndex(0)->getUuid(); + auto nonExistentUuid = QUuid::createUuid(); + + // Act + auto success = folderService->moveFolder(firstFolderUuid, nonExistentUuid); + + // Assert + ASSERT_FALSE(success); +} + +TEST_F(AFolderService, FailsMovingAFolderIfDestinationDoesNotExist) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + auto firstFolderUuid = + folderService->getRootFolder()->getChildAtIndex(0)->getUuid(); + auto nonExistentUuid = QUuid::createUuid(); + + // Act + auto success = folderService->moveFolder(nonExistentUuid, firstFolderUuid); + + // Assert + ASSERT_FALSE(success); +} + +TEST_F(AFolderService, FailsMovingAFolderIfDestinationAlreadyIsTheParent) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(1); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(1); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + auto firstFolderUuid = + folderService->getRootFolder()->getChildAtIndex(0)->getUuid(); + auto rootFolderUuid = folderService->getRootFolder()->getUuid(); + + // Act + auto success = folderService->moveFolder(firstFolderUuid, rootFolderUuid); + + // Assert + ASSERT_FALSE(success); +} + +TEST_F(AFolderService, FailsMovingAFolderIfDestinationIsADescendant) +{ + // Expect + EXPECT_CALL(folderStorageGatewayMock, updateFolder(_, _)).Times(2); + EXPECT_CALL(localLibraryTrackerMock, saveFolders(_)).Times(2); + + // Arrange + folderService->createFolder("first", "color", "icon", "description", + QUuid()); + auto firstFolderUuid = + folderService->getRootFolder()->getChildAtIndex(0)->getUuid(); + + folderService->createFolder("second", "color", "icon", "description", + firstFolderUuid); + auto secondFolderUuid = folderService->getFolder(firstFolderUuid) + ->getChildAtIndex(0) + ->getUuid(); + + // Act + auto success = folderService->moveFolder(firstFolderUuid, secondFolderUuid); + + // Assert + ASSERT_FALSE(success); +} + +TEST_F(AFolderService, SucceedsSettingUpUserData) +{ + // Arrange + Folder rootFolder("ROOT", "", "", ""); + rootFolder.setLastModified(QDateTime::currentDateTimeUtc().addDays(-2)); + + auto child1 = + std::make_unique("SomeFolder", "default", "folder", ""); + child1->setIndexInParent(0); + rootFolder.addChild(std::move(child1)); + + auto child2 = + std::make_unique("AnotherFolder", "default", "car", ""); + child2->setIndexInParent(1); + rootFolder.addChild(std::move(child2)); + + EXPECT_CALL(localLibraryTrackerMock, loadFolders()) + .WillOnce(Return(rootFolder)); + + // Act + folderService->setupUserData("someToken", "someEmail@librum.com"); + + // Assert + ASSERT_EQ(folderService->getRootFolder()->childCount(), 2); + ASSERT_EQ(folderService->getRootFolder()->getUuid(), rootFolder.getUuid()); +} + +TEST_F(AFolderService, SucceedsSettingUpUserDataWhenNoLocalLibraryExists) +{ + // Arrange + Folder invalidFolder("invalid", "", "", ""); + + EXPECT_CALL(localLibraryTrackerMock, loadFolders()) + .WillOnce(Return(invalidFolder)); + + // Act + folderService->setupUserData("someToken", "someEmail@librum.com"); + + // Assert + ASSERT_EQ(folderService->getRootFolder()->childCount(), 1); + ASSERT_EQ(folderService->getRootFolder()->getChildAtIndex(0)->getName(), + "Archive"); +} + +}; // namespace tests::application \ No newline at end of file