diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp index 89a59ac5..b9dcdfbc 100644 --- a/src/akonadi/akonadicachingstorage.cpp +++ b/src/akonadi/akonadicachingstorage.cpp @@ -1,381 +1,381 @@ /* This file is part of Zanshin Copyright 2015 Mario Bensi Copyright 2017 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadicachingstorage.h" #include "akonadistorage.h" #include "akonadicollectionfetchjobinterface.h" #include "akonadiitemfetchjobinterface.h" #include using namespace Akonadi; class CachingCollectionFetchJob : public KCompositeJob, public CollectionFetchJobInterface { Q_OBJECT public: CachingCollectionFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, const Collection &collection, StorageInterface::FetchDepth depth, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache), m_collection(collection), m_depth(depth) { QTimer::singleShot(0, this, &CachingCollectionFetchJob::start); } void start() override { if (m_started) return; if (m_cache->isCollectionListPopulated()) { QTimer::singleShot(0, this, &CachingCollectionFetchJob::retrieveFromCache); } else { auto job = m_storage->fetchCollections(Akonadi::Collection::root(), Akonadi::StorageInterface::Recursive); job->setResource(m_resource); addSubjob(job->kjob()); } m_started = true; } Collection::List collections() const override { const auto isInputCollection = [this] (const Collection &collection) { return collection.id() == m_collection.id() || (!m_collection.remoteId().isEmpty() && collection.remoteId() == m_collection.remoteId()); }; if (m_depth == StorageInterface::Base) { auto it = std::find_if(m_collections.cbegin(), m_collections.cend(), isInputCollection); if (it != m_collections.cend()) return Collection::List() << *it; else return Collection::List(); } auto collections = m_collections; auto it = collections.begin(); if (m_depth == StorageInterface::FirstLevel) { it = std::remove_if(collections.begin(), collections.end(), [isInputCollection] (const Collection &collection) { return !isInputCollection(collection.parentCollection()); }); } else { it = std::remove_if(collections.begin(), collections.end(), [isInputCollection] (const Collection &collection) { auto parent = collection.parentCollection(); while (parent.isValid() && !isInputCollection(parent)) parent = parent.parentCollection(); return !isInputCollection(parent); }); } collections.erase(it, collections.end()); return collections; } void setResource(const QString &resource) override { m_resource = resource; } private: void slotResult(KJob *kjob) override { if (kjob->error()) { KCompositeJob::slotResult(kjob); return; } auto job = dynamic_cast(kjob); Q_ASSERT(job); auto cachedCollections = job->collections(); for (const auto &collection : job->collections()) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { if (!cachedCollections.contains(parent)) { cachedCollections.append(parent); } parent = parent.parentCollection(); } } m_cache->setCollections(cachedCollections); m_collections = job->collections(); emitResult(); } void retrieveFromCache() { m_collections = m_cache->collections(); emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; QString m_resource; const Collection m_collection; const StorageInterface::FetchDepth m_depth; Collection::List m_collections; }; class CachingCollectionItemsFetchJob : public KCompositeJob, public ItemFetchJobInterface { Q_OBJECT public: CachingCollectionItemsFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, const Collection &collection, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache), m_collection(collection) { QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::start); } void start() override { if (m_started) return; if (m_cache->isCollectionPopulated(m_collection.id())) { QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::retrieveFromCache); } else { auto job = m_storage->fetchItems(m_collection, this); addSubjob(job->kjob()); } m_started = true; } Item::List items() const override { return m_items; } void setCollection(const Collection &collection) override { m_collection = collection; } private: void slotResult(KJob *kjob) override { if (kjob->error()) { KCompositeJob::slotResult(kjob); return; } auto job = dynamic_cast(kjob); Q_ASSERT(job); m_items = job->items(); m_cache->populateCollection(m_collection, m_items); emitResult(); } void retrieveFromCache() { m_items = m_cache->items(m_collection); emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; Collection m_collection; Item::List m_items; }; class CachingSingleItemFetchJob : public KCompositeJob, public ItemFetchJobInterface { Q_OBJECT public: CachingSingleItemFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, const Item &item, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache), m_item(item) { QTimer::singleShot(0, this, &CachingSingleItemFetchJob::start); } void start() override { if (m_started) return; const auto item = m_cache->item(m_item.id()); if (item.isValid()) { QTimer::singleShot(0, this, [this, item] { retrieveFromCache(item); }); } else { auto job = m_storage->fetchItem(m_item, this); job->setCollection(m_collection); addSubjob(job->kjob()); } m_started = true; } Item::List items() const override { return m_items; } void setCollection(const Collection &collection) override { m_collection = collection; } private: void slotResult(KJob *kjob) override { if (kjob->error()) { KCompositeJob::slotResult(kjob); return; } auto job = dynamic_cast(kjob); Q_ASSERT(job); m_items = job->items(); emitResult(); } void retrieveFromCache(const Item &item) { m_items = Item::List() << item; emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; Item m_item; Collection m_collection; Item::List m_items; }; CachingStorage::CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage) : m_cache(cache), m_storage(storage) { } CachingStorage::~CachingStorage() { } Collection CachingStorage::defaultCollection() { return m_storage->defaultCollection(); } KJob *CachingStorage::createItem(Item item, Collection collection) { return m_storage->createItem(item, collection); } KJob *CachingStorage::updateItem(Item item, QObject *parent) { return m_storage->updateItem(item, parent); } KJob *CachingStorage::removeItem(Item item, QObject *parent) { return m_storage->removeItem(item, parent); } KJob *CachingStorage::removeItems(Item::List items, QObject *parent) { return m_storage->removeItems(items, parent); } KJob *CachingStorage::moveItem(Item item, Collection collection, QObject *parent) { return m_storage->moveItem(item, collection, parent); } KJob *CachingStorage::moveItems(Item::List items, Collection collection, QObject *parent) { return m_storage->moveItems(items, collection, parent); } KJob *CachingStorage::createCollection(Collection collection, QObject *parent) { return m_storage->createCollection(collection, parent); } KJob *CachingStorage::updateCollection(Collection collection, QObject *parent) { return m_storage->updateCollection(collection, parent); } KJob *CachingStorage::removeCollection(Collection collection, QObject *parent) { return m_storage->removeCollection(collection, parent); } -KJob *CachingStorage::createTransaction() +KJob *CachingStorage::createTransaction(QObject *parent) { - return m_storage->createTransaction(); + return m_storage->createTransaction(parent); } CollectionFetchJobInterface *CachingStorage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth) { return new CachingCollectionFetchJob(m_storage, m_cache, collection, depth); } ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection, QObject *parent) { return new CachingCollectionItemsFetchJob(m_storage, m_cache, collection, parent); } ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item, QObject *parent) { return new CachingSingleItemFetchJob(m_storage, m_cache, item, parent); } #include "akonadicachingstorage.moc" diff --git a/src/akonadi/akonadicachingstorage.h b/src/akonadi/akonadicachingstorage.h index 576f3d1c..4d3e9ce1 100644 --- a/src/akonadi/akonadicachingstorage.h +++ b/src/akonadi/akonadicachingstorage.h @@ -1,65 +1,65 @@ /* This file is part of Zanshin Copyright 2015 Mario Bensi Copyright 2017 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_CACHING_STORAGE_H #define AKONADI_CACHING_STORAGE_H #include "akonadistorageinterface.h" #include "akonadicache.h" namespace Akonadi { class CachingStorage : public StorageInterface { public: explicit CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage); virtual ~CachingStorage(); Akonadi::Collection defaultCollection() override; KJob *createItem(Item item, Collection collection) override; KJob *updateItem(Item item, QObject *parent) override; KJob *removeItem(Akonadi::Item item, QObject *parent) override; KJob *removeItems(Item::List items, QObject *parent) override; KJob *moveItem(Item item, Collection collection, QObject *parent) override; KJob *moveItems(Item::List item, Collection collection, QObject *parent = nullptr) override; KJob *createCollection(Collection collection, QObject *parent) override; KJob *updateCollection(Collection collection, QObject *parent) override; KJob *removeCollection(Collection collection, QObject *parent) override; - KJob *createTransaction() override; + KJob *createTransaction(QObject *parent) override; CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; ItemFetchJobInterface *fetchItems(Akonadi::Collection collection, QObject *parent) override; ItemFetchJobInterface *fetchItem(Akonadi::Item item, QObject *parent) override; private: Cache::Ptr m_cache; StorageInterface::Ptr m_storage; }; } #endif // AKONADI_CACHING_STORAGE_H diff --git a/src/akonadi/akonadiprojectrepository.cpp b/src/akonadi/akonadiprojectrepository.cpp index 5f75cb74..3eb87448 100644 --- a/src/akonadi/akonadiprojectrepository.cpp +++ b/src/akonadi/akonadiprojectrepository.cpp @@ -1,139 +1,139 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiprojectrepository.h" #include "akonadiitemfetchjobinterface.h" #include "utils/compositejob.h" using namespace Akonadi; ProjectRepository::ProjectRepository(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer) : m_storage(storage), m_serializer(serializer) { } KJob *ProjectRepository::create(Domain::Project::Ptr project, Domain::DataSource::Ptr source) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(!item.isValid()); auto collection = m_serializer->createCollectionFromDataSource(source); Q_ASSERT(collection.isValid()); return m_storage->createItem(item, collection); } KJob *ProjectRepository::update(Domain::Project::Ptr project) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(item.isValid()); return m_storage->updateItem(item, this); } KJob *ProjectRepository::remove(Domain::Project::Ptr project) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(item.isValid()); return m_storage->removeItem(item, this); } KJob *ProjectRepository::associate(Domain::Project::Ptr parent, Domain::Task::Ptr child) { Item childItem = m_serializer->createItemFromTask(child); Q_ASSERT(childItem.isValid()); auto job = new Utils::CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem, this); job->install(fetchItemJob->kjob(), [fetchItemJob, parent, child, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->updateItemProject(childItem, parent); // Check collections to know if we need to move child auto parentItem = m_serializer->createItemFromProject(parent); ItemFetchJobInterface *fetchParentItemJob = m_storage->fetchItem(parentItem, this); job->install(fetchParentItemJob->kjob(), [fetchParentItemJob, child, childItem, job, this] { if (fetchParentItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchParentItemJob->items().size() == 1); auto parentItem = fetchParentItemJob->items().at(0); const int itemCollectionId = childItem.parentCollection().id(); const int parentCollectionId = parentItem.parentCollection().id(); if (itemCollectionId != parentCollectionId) { ItemFetchJobInterface *fetchChildrenItemJob = m_storage->fetchItems(childItem.parentCollection(), this); job->install(fetchChildrenItemJob->kjob(), [fetchChildrenItemJob, childItem, parentItem, job, this] { if (fetchChildrenItemJob->kjob()->error() != KJob::NoError) return; Item::List childItems = m_serializer->filterDescendantItems(fetchChildrenItemJob->items(), childItem); - auto transaction = m_storage->createTransaction(); + auto transaction = m_storage->createTransaction(this); m_storage->updateItem(childItem, transaction); childItems.push_front(childItem); m_storage->moveItems(childItems, parentItem.parentCollection(), transaction); job->addSubjob(transaction); transaction->start(); }); } else { auto updateJob = m_storage->updateItem(childItem, this); job->addSubjob(updateJob); updateJob->start(); } }); }); return job; } KJob *ProjectRepository::dissociate(Domain::Task::Ptr child) { auto job = new Utils::CompositeJob(); const auto childItem = m_serializer->createItemFromTask(child); Q_ASSERT(childItem.isValid()); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem, this); job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->removeItemParent(childItem); auto updateJob = m_storage->updateItem(childItem, this); job->addSubjob(updateJob); updateJob->start(); }); return job; } diff --git a/src/akonadi/akonadistorage.cpp b/src/akonadi/akonadistorage.cpp index 677614f2..8ca2edbb 100644 --- a/src/akonadi/akonadistorage.cpp +++ b/src/akonadi/akonadistorage.cpp @@ -1,269 +1,269 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadistorage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonadistoragesettings.h" using namespace Akonadi; class CollectionJob : public CollectionFetchJob, public CollectionFetchJobInterface { Q_OBJECT public: CollectionJob(const Collection &collection, Type type = FirstLevel, QObject *parent = nullptr) : CollectionFetchJob(collection, type, parent), m_collection(collection), m_type(type) { } Collection::List collections() const override { auto collections = CollectionFetchJob::collections(); // Memorize them to reconstruct the ancestor chain later QMap collectionsMap; collectionsMap[m_collection.id()] = m_collection; foreach (const auto &collection, collections) { collectionsMap[collection.id()] = collection; } // Why the hell isn't fetchScope() const and returning a reference??? auto self = const_cast(this); const auto allowedMimeTypes = self->fetchScope().contentMimeTypes().toSet(); if (!allowedMimeTypes.isEmpty()) { collections.erase(std::remove_if(collections.begin(), collections.end(), [allowedMimeTypes] (const Collection &collection) { auto mimeTypes = collection.contentMimeTypes().toSet(); return !mimeTypes.intersects(allowedMimeTypes); }), collections.end()); } if (m_type != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data std::function reconstructAncestors = [collectionsMap, &reconstructAncestors, this] (const Collection &collection) -> Collection { Q_ASSERT(collection.isValid()); if (collection == m_collection) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(collectionsMap[parent.id()]); auto result = collection; result.setParentCollection(reconstructedParent); return result; }; std::transform(collections.begin(), collections.end(), collections.begin(), reconstructAncestors); } return collections; } void setResource(const QString &resource) override { fetchScope().setResource(resource); } private: const Collection m_collection; const Type m_type; }; class ItemJob : public ItemFetchJob, public ItemFetchJobInterface { Q_OBJECT public: using ItemFetchJob::ItemFetchJob; Item::List items() const override { return ItemFetchJob::items(); } void setCollection(const Collection &collection) override { ItemFetchJob::setCollection(collection); } }; Storage::Storage() { } Storage::~Storage() { } Collection Storage::defaultCollection() { return StorageSettings::instance().defaultCollection(); } KJob *Storage::createItem(Item item, Collection collection) { return new ItemCreateJob(item, collection); } KJob *Storage::updateItem(Item item, QObject *parent) { return new ItemModifyJob(item, parent); } KJob *Storage::removeItem(Item item, QObject *parent) { return new ItemDeleteJob(item, parent); } KJob *Storage::removeItems(Item::List items, QObject *parent) { return new ItemDeleteJob(items, parent); } KJob *Storage::moveItem(Item item, Collection collection, QObject *parent) { return new ItemMoveJob(item, collection, parent); } KJob *Storage::moveItems(Item::List items, Collection collection, QObject *parent) { return new ItemMoveJob(items, collection, parent); } KJob *Storage::createCollection(Collection collection, QObject *parent) { return new CollectionCreateJob(collection, parent); } KJob *Storage::updateCollection(Collection collection, QObject *parent) { return new CollectionModifyJob(collection, parent); } KJob *Storage::removeCollection(Collection collection, QObject *parent) { return new CollectionDeleteJob(collection, parent); } -KJob *Storage::createTransaction() +KJob *Storage::createTransaction(QObject *parent) { - return new TransactionSequence(); + return new TransactionSequence(parent); } CollectionFetchJobInterface *Storage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth) { auto job = new CollectionJob(collection, jobTypeFromDepth(depth)); auto scope = job->fetchScope(); scope.setContentMimeTypes({KCalCore::Todo::todoMimeType()}); scope.setIncludeStatistics(true); scope.setAncestorRetrieval(CollectionFetchScope::All); scope.setListFilter(Akonadi::CollectionFetchScope::Display); job->setFetchScope(scope); return job; } ItemFetchJobInterface *Storage::fetchItems(Collection collection, QObject *parent) { auto job = new ItemJob(collection, parent); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchItem(Akonadi::Item item, QObject *parent) { auto job = new ItemJob(item, parent); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchItemsWithTags(Collection collection) { auto job = new ItemJob(collection); configureItemFetchJob(job); job->fetchScope().setFetchTags(true); job->fetchScope().tagFetchScope().setFetchIdOnly(false); return job; } CollectionFetchJob::Type Storage::jobTypeFromDepth(StorageInterface::FetchDepth depth) { auto jobType = CollectionJob::Base; switch (depth) { case Base: jobType = CollectionJob::Base; break; case FirstLevel: jobType = CollectionJob::FirstLevel; break; case Recursive: jobType = CollectionJob::Recursive; break; default: qFatal("Unexpected enum value"); break; } return jobType; } void Storage::configureItemFetchJob(ItemJob *job) { auto scope = job->fetchScope(); scope.fetchFullPayload(); scope.fetchAllAttributes(); scope.setFetchTags(false); scope.setAncestorRetrieval(ItemFetchScope::All); job->setFetchScope(scope); } #include "akonadistorage.moc" diff --git a/src/akonadi/akonadistorage.h b/src/akonadi/akonadistorage.h index fd21a621..217d751e 100644 --- a/src/akonadi/akonadistorage.h +++ b/src/akonadi/akonadistorage.h @@ -1,68 +1,68 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_STORAGE_H #define AKONADI_STORAGE_H #include "akonadistorageinterface.h" #include class ItemJob; namespace Akonadi { class Storage : public StorageInterface { public: Storage(); virtual ~Storage(); Akonadi::Collection defaultCollection() override; KJob *createItem(Item item, Collection collection) override; KJob *updateItem(Item item, QObject *parent) override; KJob *removeItem(Akonadi::Item item, QObject *parent) override; KJob *removeItems(Item::List items, QObject *parent) override; KJob *moveItem(Item item, Collection collection, QObject *parent) override; KJob *moveItems(Item::List item, Collection collection, QObject *parent) override; KJob *createCollection(Collection collection, QObject *parent) override; KJob *updateCollection(Collection collection, QObject *parent) override; KJob *removeCollection(Collection collection, QObject *parent) override; - KJob *createTransaction() override; + KJob *createTransaction(QObject *parent) override; CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; ItemFetchJobInterface *fetchItems(Akonadi::Collection collection, QObject *parent) override; ItemFetchJobInterface *fetchItem(Akonadi::Item item, QObject *parent) override; ItemFetchJobInterface *fetchItemsWithTags(Akonadi::Collection collection); private: CollectionFetchJob::Type jobTypeFromDepth(StorageInterface::FetchDepth depth); void configureItemFetchJob(ItemJob *job); }; } #endif // AKONADI_STORAGE_H diff --git a/src/akonadi/akonadistorageinterface.h b/src/akonadi/akonadistorageinterface.h index 97466240..6171436a 100644 --- a/src/akonadi/akonadistorageinterface.h +++ b/src/akonadi/akonadistorageinterface.h @@ -1,77 +1,77 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_STORAGEINTERFACE_H #define AKONADI_STORAGEINTERFACE_H #include #include class KJob; class QObject; class QByteArray; namespace Akonadi { class Collection; class CollectionFetchJobInterface; class ItemFetchJobInterface; class StorageInterface { public: typedef QSharedPointer Ptr; enum FetchDepth { Base, FirstLevel, Recursive }; StorageInterface(); virtual ~StorageInterface(); virtual Akonadi::Collection defaultCollection() = 0; virtual KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) = 0; virtual KJob *updateItem(Akonadi::Item item, QObject *parent) = 0; virtual KJob *removeItem(Akonadi::Item item, QObject *parent) = 0; virtual KJob *removeItems(Item::List items, QObject *parent) = 0; virtual KJob *moveItem(Item item, Collection collection, QObject *parent) = 0; virtual KJob *moveItems(Item::List item, Collection collection, QObject *parent) = 0; virtual KJob *createCollection(Collection collection, QObject *parent) = 0; virtual KJob *updateCollection(Collection collection, QObject *parent) = 0; virtual KJob *removeCollection(Collection collection, QObject *parent) = 0; - virtual KJob *createTransaction() = 0; + virtual KJob *createTransaction(QObject *parent) = 0; virtual CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) = 0; virtual ItemFetchJobInterface *fetchItems(Akonadi::Collection collection, QObject *parent) = 0; virtual ItemFetchJobInterface *fetchItem(Akonadi::Item item, QObject *parent) = 0; }; } #endif // AKONADI_STORAGEINTERFACE_H diff --git a/src/akonadi/akonaditaskrepository.cpp b/src/akonadi/akonaditaskrepository.cpp index bcf35ff9..3d69a336 100644 --- a/src/akonadi/akonaditaskrepository.cpp +++ b/src/akonadi/akonaditaskrepository.cpp @@ -1,303 +1,303 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonaditaskrepository.h" #include #include #include "akonadicollectionfetchjobinterface.h" #include "akonadiitemfetchjobinterface.h" #include "utils/compositejob.h" using namespace Akonadi; using namespace Utils; TaskRepository::TaskRepository(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer) : m_storage(storage), m_serializer(serializer) { } KJob *TaskRepository::createItem(const Item &item) { const Akonadi::Collection defaultCollection = m_storage->defaultCollection(); if (defaultCollection.isValid()) { return m_storage->createItem(item, defaultCollection); } else { auto job = new CompositeJob(); CollectionFetchJobInterface *fetchCollectionJob = m_storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive); job->install(fetchCollectionJob->kjob(), [fetchCollectionJob, item, job, this] { if (fetchCollectionJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchCollectionJob->collections().size() > 0); const Akonadi::Collection::List collections = fetchCollectionJob->collections(); auto it = std::find_if(collections.constBegin(), collections.constEnd(), [] (const Akonadi::Collection &c) { return (c.rights() & Akonadi::Collection::CanCreateItem) && (c.rights() & Akonadi::Collection::CanChangeItem) && (c.rights() & Akonadi::Collection::CanDeleteItem); }); if (it == collections.constEnd()) { job->emitError(i18n("Could not find a collection to store the task into!")); } else { auto col = *it; Q_ASSERT(col.isValid()); auto createJob = m_storage->createItem(item, col); job->addSubjob(createJob); createJob->start(); } }); return job; } } KJob *TaskRepository::create(Domain::Task::Ptr task) { auto item = m_serializer->createItemFromTask(task); Q_ASSERT(!item.isValid()); return createItem(item); } KJob *TaskRepository::createChild(Domain::Task::Ptr task, Domain::Task::Ptr parent) { Item taskItem = m_serializer->createItemFromTask(task); Q_ASSERT(!taskItem.isValid()); Item parentItem = m_serializer->createItemFromTask(parent); Q_ASSERT(parentItem.isValid()); Q_ASSERT(parentItem.parentCollection().isValid()); m_serializer->updateItemParent(taskItem, parent); return m_storage->createItem(taskItem, parentItem.parentCollection()); } KJob *TaskRepository::createInProject(Domain::Task::Ptr task, Domain::Project::Ptr project) { Item taskItem = m_serializer->createItemFromTask(task); Q_ASSERT(!taskItem.isValid()); Item projectItem = m_serializer->createItemFromProject(project); Q_ASSERT(projectItem.isValid()); Q_ASSERT(projectItem.parentCollection().isValid()); m_serializer->updateItemProject(taskItem, project); return m_storage->createItem(taskItem, projectItem.parentCollection()); } KJob *TaskRepository::createInContext(Domain::Task::Ptr task, Domain::Context::Ptr context) { Item item = m_serializer->createItemFromTask(task); Q_ASSERT(!item.isValid()); m_serializer->addContextToTask(context, item); return createItem(item); } KJob *TaskRepository::update(Domain::Task::Ptr task) { auto item = m_serializer->createItemFromTask(task); Q_ASSERT(item.isValid()); return m_storage->updateItem(item, this); } KJob *TaskRepository::remove(Domain::Task::Ptr task) { auto item = m_serializer->createItemFromTask(task); Q_ASSERT(item.isValid()); auto compositeJob = new CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(item, this); compositeJob->install(fetchItemJob->kjob(), [fetchItemJob, compositeJob, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto item = fetchItemJob->items().at(0); ItemFetchJobInterface *fetchCollectionItemsJob = m_storage->fetchItems(item.parentCollection(), this); compositeJob->install(fetchCollectionItemsJob->kjob(), [fetchCollectionItemsJob, item, compositeJob, this] { if (fetchCollectionItemsJob->kjob()->error() != KJob::NoError) return; Item::List childItems = m_serializer->filterDescendantItems(fetchCollectionItemsJob->items(), item); childItems << item; auto removeJob = m_storage->removeItems(childItems, this); compositeJob->addSubjob(removeJob); removeJob->start(); }); }); return compositeJob; } KJob *TaskRepository::promoteToProject(Domain::Task::Ptr task) { auto item = m_serializer->createItemFromTask(task); auto job = new CompositeJob(); auto fetchJob = m_storage->fetchItem(item, this); job->install(fetchJob->kjob(), [fetchJob, job, this] { if (fetchJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchJob->items().size() == 1); auto item = fetchJob->items().at(0); m_serializer->promoteItemToProject(item); auto updateJob = m_storage->updateItem(item, this); job->addSubjob(updateJob); updateJob->start(); }); return job; } KJob *TaskRepository::associate(Domain::Task::Ptr parent, Domain::Task::Ptr child) { auto childItem = m_serializer->createItemFromTask(child); auto job = new CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem, this); job->install(fetchItemJob->kjob(), [fetchItemJob, child, parent, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->updateItemParent(childItem, parent); // Check collections to know if we need to move child auto partialParentItem = m_serializer->createItemFromTask(parent); ItemFetchJobInterface *fetchParentItemJob = m_storage->fetchItems(partialParentItem.parentCollection(), this); job->install(fetchParentItemJob->kjob(), [child, parent, fetchParentItemJob, partialParentItem, childItem, job, this] { if (fetchParentItemJob->kjob()->error() != KJob::NoError) return; const auto items = fetchParentItemJob->items(); const auto parentIndex = items.indexOf(partialParentItem); Q_ASSERT(parentIndex >= 0); const auto parentItem = items.at(parentIndex); const auto childUid = m_serializer->itemUid(childItem); auto relatedUid = m_serializer->relatedUidFromItem(parentItem); while (!relatedUid.isEmpty()) { if (relatedUid == childUid) { job->emitError(i18n("Could not associate '%1', it is an ancestor of '%2'", child->title(), parent->title())); return; } auto it = std::find_if(items.constBegin(), items.constEnd(), [relatedUid, this] (const Akonadi::Item &item) { return m_serializer->itemUid(item) == relatedUid; }); if (it == items.end()) break; relatedUid = m_serializer->relatedUidFromItem(*it); } const int itemCollectionId = childItem.parentCollection().id(); const int parentCollectionId = parentItem.parentCollection().id(); if (itemCollectionId != parentCollectionId) { ItemFetchJobInterface *fetchChildrenItemJob = m_storage->fetchItems(childItem.parentCollection(), this); job->install(fetchChildrenItemJob->kjob(), [fetchChildrenItemJob, childItem, parentItem, job, this] { if (fetchChildrenItemJob->kjob()->error() != KJob::NoError) return; Item::List childItems = m_serializer->filterDescendantItems(fetchChildrenItemJob->items(), childItem); - auto transaction = m_storage->createTransaction(); + auto transaction = m_storage->createTransaction(this); m_storage->updateItem(childItem, transaction); childItems.push_front(childItem); m_storage->moveItems(childItems, parentItem.parentCollection(), transaction); job->addSubjob(transaction); transaction->start(); }); } else { auto updateJob = m_storage->updateItem(childItem, this); job->addSubjob(updateJob); updateJob->start(); } }); }); return job; } KJob *TaskRepository::dissociate(Domain::Task::Ptr child) { auto job = new CompositeJob(); auto childItem = m_serializer->createItemFromTask(child); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem, this); job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->removeItemParent(childItem); auto updateJob = m_storage->updateItem(childItem, this); job->addSubjob(updateJob); updateJob->start(); }); return job; } KJob *TaskRepository::dissociateAll(Domain::Task::Ptr child) { auto job = new CompositeJob(); auto childItem = m_serializer->createItemFromTask(child); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem, this); job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->removeItemParent(childItem); m_serializer->clearItem(&childItem); auto updateJob = m_storage->updateItem(childItem, this); job->addSubjob(updateJob); updateJob->start(); }); return job; } diff --git a/tests/testlib/akonadifakestorage.cpp b/tests/testlib/akonadifakestorage.cpp index 51774ad7..56907bb4 100644 --- a/tests/testlib/akonadifakestorage.cpp +++ b/tests/testlib/akonadifakestorage.cpp @@ -1,430 +1,430 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadifakestorage.h" #include #include "akonadi/akonadistoragesettings.h" #include "akonadifakedata.h" #include "akonadifakejobs.h" #include "utils/jobhandler.h" using namespace Testlib; class AkonadiFakeTransaction : public FakeJob { Q_OBJECT public: - explicit AkonadiFakeTransaction() - : FakeJob(), + explicit AkonadiFakeTransaction(QObject *parent = nullptr) + : FakeJob(parent), m_nextIdx(0) { } private slots: void onTimeout() override { auto jobs = childJobs(); if (m_nextIdx == 0) { const auto it = std::find_if(jobs.constBegin(), jobs.constEnd(), [] (FakeJob *job) { return job->expectedError() != 0; }); if (it != jobs.constEnd()) { setError((*it)->expectedError()); setErrorText((*it)->expectedErrorText()); emitResult(); return; } } if (m_nextIdx >= jobs.size()) { emitResult(); return; } auto job = jobs[m_nextIdx]; connect(job, &KJob::result, this, &AkonadiFakeTransaction::onTimeout); job->start(); m_nextIdx++; } private: QList childJobs() const { QList jobs = findChildren(); jobs.erase(std::remove_if(jobs.begin(), jobs.end(), [this] (FakeJob *job) { return job->parent() != this; }), jobs.end()); return jobs; } int m_nextIdx; }; Utils::JobHandler::StartMode startModeForParent(QObject *parent) { bool isTransaction = qobject_cast(parent); return isTransaction ? Utils::JobHandler::ManualStart : Utils::JobHandler::AutoStart; } void noop() {} AkonadiFakeStorage::AkonadiFakeStorage(AkonadiFakeData *data) : m_data(data) { } Akonadi::Collection AkonadiFakeStorage::defaultCollection() { return Akonadi::StorageSettings::instance().defaultCollection(); } KJob *AkonadiFakeStorage::createItem(Akonadi::Item item, Akonadi::Collection collection) { Q_ASSERT(!item.isValid()); auto job = new FakeJob; if (!m_data->item(item.id()).isValid()) { job->setExpectedError(m_data->storageBehavior().createNextItemErrorCode(), m_data->storageBehavior().createNextItemErrorText()); Utils::JobHandler::install(job, [=] () mutable { if (!job->error()) { item.setId(m_data->maxItemId() + 1); item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->createItem(item); } }); } else { job->setExpectedError(1, QStringLiteral("Item already exists")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::updateItem(Akonadi::Item item, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid()) { job->setExpectedError(m_data->storageBehavior().updateNextItemErrorCode(), m_data->storageBehavior().updateNextItemErrorText()); Utils::JobHandler::install(job, [=] () mutable { if (!job->error()) { // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeItem(Akonadi::Item item, QObject *parent) { auto job = new FakeJob(parent); if (m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] { if (!job->error()) { m_data->removeItem(item); } }); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::removeItems(Akonadi::Item::List items, QObject *parent) { auto job = new FakeJob; auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist) { job->setExpectedError(m_data->storageBehavior().deleteNextItemErrorCode(), m_data->storageBehavior().deleteNextItemErrorText()); Utils::JobHandler::install(job, [=] { if (!job->error()) { foreach (const Akonadi::Item &item, items) { m_data->removeItem(item); } } }, startMode); } else { job->setExpectedError(1, QStringLiteral("At least one item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid() && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { if (!job->error()) { item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("The item or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent) { using namespace std::placeholders; auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { if (!job->error()) { std::transform(items.constBegin(), items.constEnd(), items.begin(), [=] (const Akonadi::Item &item) { auto result = item; result.setParentCollection(collection); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); foreach (const Akonadi::Item &item, items) { m_data->modifyItem(item); } } }, startMode); } else { job->setExpectedError(1, QStringLiteral("One of the items or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::createCollection(Akonadi::Collection collection, QObject *parent) { Q_ASSERT(!collection.isValid()); auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (!m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { if (!job->error()) { collection.setId(m_data->maxCollectionId() + 1); m_data->createCollection(collection); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection already exists")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::updateCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { if (!job->error()) { m_data->modifyCollection(collection); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { if (!job->error()) { m_data->removeCollection(collection); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } -KJob *AkonadiFakeStorage::createTransaction() +KJob *AkonadiFakeStorage::createTransaction(QObject *parent) { - auto job = new AkonadiFakeTransaction; + auto job = new AkonadiFakeTransaction(parent); Utils::JobHandler::install(job, noop); return job; } Akonadi::CollectionFetchJobInterface *AkonadiFakeStorage::fetchCollections(Akonadi::Collection collection, Akonadi::StorageInterface::FetchDepth depth) { auto job = new AkonadiFakeCollectionFetchJob; auto children = Akonadi::Collection::List(); switch (depth) { case Base: children << m_data->collection(findId(collection)); break; case FirstLevel: children << m_data->childCollections(findId(collection)); break; case Recursive: children = collectChildren(collection); break; } auto collections = children; if (depth != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data using namespace std::placeholders; auto completeCollection = std::bind(&AkonadiFakeData::reconstructAncestors, m_data, _1, collection); std::transform(collections.begin(), collections.end(), collections.begin(), completeCollection); } const auto behavior = m_data->storageBehavior().fetchCollectionsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setCollections(collections); job->setExpectedError(m_data->storageBehavior().fetchCollectionsErrorCode(collection.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItems(Akonadi::Collection collection, QObject *parent) { auto items = m_data->childItems(findId(collection)); std::transform(items.begin(), items.end(), items.begin(), [this] (const Akonadi::Item &item) { auto result = m_data->reconstructItemDependencies(item); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); auto job = new AkonadiFakeItemFetchJob(parent); const auto behavior = m_data->storageBehavior().fetchItemsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(items); job->setExpectedError(m_data->storageBehavior().fetchItemsErrorCode(collection.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItem(Akonadi::Item item, QObject *parent) { auto fullItem = m_data->item(findId(item)); fullItem = m_data->reconstructItemDependencies(fullItem); // Force payload detach fullItem.setPayloadFromData(fullItem.payloadData()); auto job = new AkonadiFakeItemFetchJob(parent); const auto behavior = m_data->storageBehavior().fetchItemBehavior(item.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(Akonadi::Item::List() << fullItem); job->setExpectedError(m_data->storageBehavior().fetchItemErrorCode(item.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::Collection::Id AkonadiFakeStorage::findId(const Akonadi::Collection &collection) { if (collection.isValid() || collection.remoteId().isEmpty()) return collection.id(); const auto remoteId = collection.remoteId(); auto collections = m_data->collections(); auto result = std::find_if(collections.constBegin(), collections.constEnd(), [remoteId] (const Akonadi::Collection &collection) { return collection.remoteId() == remoteId; }); return (result != collections.constEnd()) ? result->id() : collection.id(); } Akonadi::Item::Id AkonadiFakeStorage::findId(const Akonadi::Item &item) { if (item.isValid() || item.remoteId().isEmpty()) return item.id(); const auto remoteId = item.remoteId(); auto items = m_data->items(); auto result = std::find_if(items.constBegin(), items.constEnd(), [remoteId] (const Akonadi::Item &item) { return item.remoteId() == remoteId; }); return (result != items.constEnd()) ? result->id() : item.id(); } Akonadi::Collection::List AkonadiFakeStorage::collectChildren(const Akonadi::Collection &root) { auto collections = Akonadi::Collection::List(); foreach (const auto &child, m_data->childCollections(findId(root))) { if (child.enabled()) collections << m_data->collection(findId(child)); collections += collectChildren(child); } return collections; } #include "akonadifakestorage.moc" diff --git a/tests/testlib/akonadifakestorage.h b/tests/testlib/akonadifakestorage.h index 761a0edc..f0220b66 100644 --- a/tests/testlib/akonadifakestorage.h +++ b/tests/testlib/akonadifakestorage.h @@ -1,67 +1,67 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTLIB_AKONADIFAKESTORAGE_H #define TESTLIB_AKONADIFAKESTORAGE_H #include "akonadi/akonadistorageinterface.h" namespace Testlib { class AkonadiFakeData; class AkonadiFakeStorage : public Akonadi::StorageInterface { public: explicit AkonadiFakeStorage(AkonadiFakeData *data); Akonadi::Collection defaultCollection() override; KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) override; KJob *updateItem(Akonadi::Item item, QObject *parent) override; KJob *removeItem(Akonadi::Item item, QObject *parent) override; KJob *removeItems(Akonadi::Item::List items, QObject *parent) override; KJob *moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent) override; KJob *moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent) override; KJob *createCollection(Akonadi::Collection collection, QObject *parent) override; KJob *updateCollection(Akonadi::Collection collection, QObject *parent) override; KJob *removeCollection(Akonadi::Collection collection, QObject *parent) override; - KJob *createTransaction() override; + KJob *createTransaction(QObject *parent) override; Akonadi::CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; Akonadi::ItemFetchJobInterface *fetchItems(Akonadi::Collection collection, QObject *parent) override; Akonadi::ItemFetchJobInterface *fetchItem(Akonadi::Item item, QObject *parent) override; private: Akonadi::Collection::Id findId(const Akonadi::Collection &collection); Akonadi::Item::Id findId(const Akonadi::Item &item); Akonadi::Collection::List collectChildren(const Akonadi::Collection &root); AkonadiFakeData *m_data; }; } #endif // TESTLIB_AKONADIFAKESTORAGE_H diff --git a/tests/testlib/akonadistoragetestbase.cpp b/tests/testlib/akonadistoragetestbase.cpp index fbe43a0f..554e8a17 100644 --- a/tests/testlib/akonadistoragetestbase.cpp +++ b/tests/testlib/akonadistoragetestbase.cpp @@ -1,830 +1,830 @@ /* This file is part of Zanshin Copyright 2014-2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadistoragetestbase.h" #include #include #include #include #include #include #include "utils/mem_fn.h" #include "AkonadiCore/qtest_akonadi.h" #include #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadistorage.h" #include "akonadi/akonadistoragesettings.h" #include "akonadi/akonaditimestampattribute.h" using namespace Testlib; AkonadiStorageTestBase::AkonadiStorageTestBase(QObject *parent) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); } void AkonadiStorageTestBase::cleanupTestCase() { // Give a chance for jobs still waiting for an event loop // run to be deleted through deleteLater() QTest::qWait(10); } void AkonadiStorageTestBase::dumpTree() { TestLib::AkonadiDebug::dumpTree(createStorage()); } void AkonadiStorageTestBase::shouldListCollections_data() { QTest::addColumn("collection"); QTest::addColumn("expectedNames"); QTest::addColumn("depth"); QTest::newRow("all") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!" }) << Akonadi::Storage::Recursive; QTest::newRow("base type") << calendar2() << QStringList({"Calendar2"}) << Akonadi::Storage::Base; QTest::newRow("firstLevel type") << calendar1() << QStringList({"Calendar2"}) << Akonadi::Storage::FirstLevel; QTest::newRow("recursive type") << calendar1() << QStringList({"Calendar2", "Calendar3"}) << Akonadi::Storage::Recursive; } void AkonadiStorageTestBase::shouldListCollections() { // GIVEN QFETCH(Akonadi::Collection, collection); QFETCH(QStringList, expectedNames); QFETCH(Akonadi::StorageInterface::FetchDepth, depth); auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(collection, depth); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); QStringList collectionNames; collectionNames.reserve(collections.size()); foreach (const auto &collection, collections) { collectionNames << collection.name(); } collectionNames.sort(); QCOMPARE(collectionNames, expectedNames); } void AkonadiStorageTestBase::shouldRetrieveAllCollectionAncestors() { // GIVEN auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); foreach (const auto &collection, collections) { auto parent = collection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); QVERIFY(!parent.displayName().isEmpty()); parent = parent.parentCollection(); } } } void AkonadiStorageTestBase::shouldListFullItemsInACollection() { // GIVEN auto storage = createStorage(); const QStringList expectedRemoteIds = { "rid-errands-context", "rid-online-context", "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{1d33862f-f274-4c67-ab6c-362d56521ff5}", "{1d33862f-f274-4c67-ab6c-362d56521ff6}", "{7824df00-2fd6-47a4-8319-52659dc82005}", "{7824df00-2fd6-47a4-8319-52659dc82006}", "{d0159c99-0d23-41fa-bb5f-tasktoremove}", }; // WHEN auto job = storage->fetchItems(calendar2(), nullptr); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { const auto rid = item.remoteId(); itemRemoteIds << rid; QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(item.modificationTime().isValid()); if (rid.endsWith("context")) { QVERIFY2(item.attributes().isEmpty(), qPrintable(rid)); QVERIFY2(item.flags().isEmpty(), qPrintable(rid)); } else { QVERIFY2(!item.attributes().isEmpty(), qPrintable(rid)); QVERIFY2(!item.flags().isEmpty(), qPrintable(rid)); } auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldNotifyCollectionAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionAdded); MonitorSpy monitorSpy(monitor.data()); // A collection Akonadi::Collection collection; collection.setParentCollection(calendar2()); collection.setName(QStringLiteral("Foo!")); collection.setContentMimeTypes(QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo")); // WHEN auto storage = createStorage(); auto job = storage->createCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyCollectionRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{1f78b360-a01b-4785-9187-75450190342c}")); QVERIFY(collection.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection= spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); } void AkonadiStorageTestBase::shouldNotifyCollectionChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // A collection with an existing id (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{28ef9f03-4ebc-4e33-970f-f379775894f9}")); QVERIFY(collection.isValid()); collection.setName(QStringLiteral("Bar!")); // WHEN auto storage = createStorage(); auto job = storage->updateCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); Akonadi::Item item = fetchItemByRID(QStringLiteral("{d0159c99-0d23-41fa-bb5f-tasktoremove}"), calendar2()); QVERIFY(item.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeItem(item, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff6}"), calendar2()); QVERIFY(item.isValid()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->updateItem(item, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldReadDefaultCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultCollection(), Akonadi::Collection(24)); } void AkonadiStorageTestBase::shouldUpdateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("new summary")); todo->setDescription(QStringLiteral("new content")); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->updateItem(item, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QCOMPARE(*notifiedItem.payload(), *todo); } void AkonadiStorageTestBase::shouldUseTransaction() { // GIVEN auto storage = createStorage(); Akonadi::Item item1 = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item1.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{5dc1aba7-eead-4254-ba7a-58e397de1179}"), calendar1()); QVERIFY(item2.isValid()); // create wrong item Akonadi::Item item3(10000); item3.setRemoteId(QStringLiteral("wrongId")); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyUpdated(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto todo = item1.payload(); todo->setSummary(QStringLiteral("Buy tomatoes")); todo = item2.payload(); todo->setSummary(QStringLiteral("Buy chocolate")); - auto transaction = storage->createTransaction(); + auto transaction = storage->createTransaction(nullptr); storage->updateItem(item1, transaction); storage->updateItem(item3, transaction); // this job should fail storage->updateItem(item2, transaction); QVERIFY(!transaction->exec()); monitorSpy.waitForStableState(); // THEN QCOMPARE(spyUpdated.size(), 0); auto job = storage->fetchItem(item1, nullptr); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item1 = job->items().at(0); job = storage->fetchItem(item2, nullptr); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item2 = job->items().at(0); QCOMPARE(item1.payload()->summary(), QStringLiteral("Buy kiwis")); QCOMPARE(item2.payload()->summary(), QStringLiteral("Buy cheese")); } void AkonadiStorageTestBase::shouldCreateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of a new item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.parentCollection(), calendar2()); QCOMPARE(*notifiedItem.payload(), *todo); } void AkonadiStorageTestBase::shouldRetrieveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item findItem = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(findItem.isValid()); // WHEN auto job = storage->fetchItem(findItem, nullptr); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QCOMPARE(items.size(), 1); const auto &item = items[0]; QCOMPARE(item.id(), findItem.id()); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldMoveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(item.isValid()); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItem(item, calendar1(), nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldMoveItems() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); QVERIFY(item.isValid()); Akonadi::Item::List list; list << item; // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItems(list, calendar1(), nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItem() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item.isValid()); //When auto job = storage->removeItem(item, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItems() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{6c7bf5b9-4136-4203-9f45-54e32ea0eacb}"), calendar1()); QVERIFY(item.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{83cf0b15-8d61-436b-97ae-4bd88fb2fef9}"), calendar1()); QVERIFY(item2.isValid()); Akonadi::Item::List list; list << item << item2; //When auto job = storage->removeItems(list, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 2); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item2.id()); } void AkonadiStorageTestBase::shouldUpdateCollection() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::EntityDisplayAttribute; attr->setDisplayName(QStringLiteral("Foo")); collection.addAttribute(attr); auto job = storage->updateCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 0); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QCOMPARE(notifiedCollection.attribute()->displayName(), attr->displayName()); } void AkonadiStorageTestBase::shouldNotifyCollectionTimestampChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN collection.attribute(Akonadi::Collection::AddIfMissing)->refreshTimestamp(); auto job = storage->updateCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); } void AkonadiStorageTestBase::shouldNotifyCollectionSelectionChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::ApplicationSelectedAttribute; attr->setSelected(false); collection.addAttribute(attr); auto job = storage->updateCollection(collection, nullptr); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); notifiedCollection = selectionSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); } Akonadi::Item AkonadiStorageTestBase::fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection) { Akonadi::Item item; item.setRemoteId(remoteId); auto job = createStorage()->fetchItem(item, nullptr); job->setCollection(collection); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Item(); } if (job->items().count() != 1) { qWarning() << "Received unexpected amount of items: " << job->items().count(); return Akonadi::Item(); } return job->items().at(0); } Akonadi::Collection AkonadiStorageTestBase::fetchCollectionByRID(const QString &remoteId) { Akonadi::Collection collection; collection.setRemoteId(remoteId); auto job = createStorage()->fetchCollections(collection, Akonadi::StorageInterface::Base); job->setResource(QStringLiteral("akonadi_knut_resource_0")); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString() << remoteId; return Akonadi::Collection(); } if (job->collections().count() != 1) { qWarning() << "Received unexpected amount of collections: " << job->collections().count(); return Akonadi::Collection(); } return job->collections().at(0); } Akonadi::Collection AkonadiStorageTestBase::calendar1() { return fetchCollectionByRID(QStringLiteral("{cdc229c7-a9b5-4d37-989d-a28e372be2a9}")); } Akonadi::Collection AkonadiStorageTestBase::calendar2() { return fetchCollectionByRID(QStringLiteral("{e682b8b5-b67c-4538-8689-6166f64177f0}")); } Akonadi::Collection AkonadiStorageTestBase::emails() { return fetchCollectionByRID(QStringLiteral("{14096930-7bfe-46ca-8fba-7c04d3b62ec8}")); } diff --git a/tests/units/akonadi/akonadiprojectrepositorytest.cpp b/tests/units/akonadi/akonadiprojectrepositorytest.cpp index 4129edf5..aead957e 100644 --- a/tests/units/akonadi/akonadiprojectrepositorytest.cpp +++ b/tests/units/akonadi/akonadiprojectrepositorytest.cpp @@ -1,360 +1,360 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "utils/mockobject.h" #include "testlib/akonadifakejobs.h" #include "testlib/akonadifakemonitor.h" #include "akonadi/akonadiprojectrepository.h" #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" using namespace mockitopp; Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) class AkonadiProjectRepositoryTest : public QObject { Q_OBJECT private slots: void shouldCreateProjectInDataSource() { // GIVEN // A project and its corresponding item already not existing in storage Akonadi::Item item; auto project = Domain::Project::Ptr::create(); // A data source and its corresponding collection existing in storage Akonadi::Collection collection(42); auto source = Domain::DataSource::Ptr::create(); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::createItem).when(item, collection) .thenReturn(itemCreateJob); // Serializer mock Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); serializerMock(&Akonadi::SerializerInterface::createCollectionFromDataSource).when(source).thenReturn(collection); // WHEN QScopedPointer repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->create(project, source)->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, collection).exactly(1)); } void shouldUpdateExistingProject() { // GIVEN // A project and its corresponding item already existing in storage Akonadi::Item item(42); Domain::Project::Ptr project(new Domain::Project); // A mock modify job auto itemModifyJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the create job storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()) .thenReturn(itemModifyJob); // Serializer mock returning the item for the project serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); // WHEN repository->update(project)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()).exactly(1)); } void shouldRemoveExistingProject() { // GIVEN // A project and its corresponding item already existing in storage Akonadi::Item item(42); auto project = Domain::Project::Ptr::create(); // A mock remove job auto itemRemoveJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the create job storageMock(&Akonadi::StorageInterface::removeItem).when(item, repository.get()) .thenReturn(itemRemoveJob); // Serializer mock returning the item for the project serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); // WHEN repository->remove(project)->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::removeItem).when(item, repository.get()).exactly(1)); } void shouldAssociateATaskToAProject_data() { QTest::addColumn("childItem"); QTest::addColumn("parentItem"); QTest::addColumn("child"); QTest::addColumn("parent"); QTest::addColumn("itemFetchJob1"); QTest::addColumn("itemFetchJob2"); QTest::addColumn("itemFetchJob3"); QTest::addColumn("execJob"); QTest::addColumn("execParentJob"); QTest::addColumn("list"); Akonadi::Collection col(40); Akonadi::Item childItem(42); childItem.setParentCollection(col); Domain::Task::Ptr childTask(new Domain::Task); Akonadi::Item parentItem(41); parentItem.setParentCollection(col); auto parent = Domain::Project::Ptr::create(); auto itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); auto itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); auto itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item::List list; QTest::newRow("nominal case (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); QTest::newRow("child job error with empty list") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); QTest::newRow("child job error with item (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); QTest::newRow("parent job error with empty list (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); QTest::newRow("parent job error with item (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Collection col2(39); Akonadi::Item parentItem2(41); parentItem2.setParentCollection(col2); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); QTest::newRow("update and move item (task)") << childItem << parentItem2 << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item childItem2(43); Akonadi::Item::List list2; list2 << childItem2; itemFetchJob3->setItems(list2); QTest::newRow("update and move item and his child (task)") << childItem << parentItem2 << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list2; } void shouldAssociateATaskToAProject() { // GIVEN QFETCH(Akonadi::Item, childItem); QFETCH(Akonadi::Item, parentItem); QFETCH(Domain::Task::Ptr, child); QFETCH(Domain::Project::Ptr, parent); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob1); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob2); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob3); QFETCH(bool, execJob); QFETCH(bool, execParentJob); QFETCH(Akonadi::Item::List, list); // A mock create job auto itemModifyJob = new FakeJob(this); auto transactionJob = new FakeJob(this); auto itemsMoveJob = new FakeJob(this); Akonadi::Item::List movedList; movedList << childItem << list; Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the create job storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()) .thenReturn(itemFetchJob1); storageMock(&Akonadi::StorageInterface::fetchItem).when(parentItem, repository.get()) .thenReturn(itemFetchJob2); if (parentItem.parentCollection().id() != childItem.parentCollection().id()) { storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection(), repository.get()) .thenReturn(itemFetchJob3); - storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob); + storageMock(&Akonadi::StorageInterface::createTransaction).when(repository.get()).thenReturn(transactionJob); storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob) .thenReturn(itemsMoveJob); } else { storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()) .thenReturn(itemModifyJob); } // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(parent).thenReturn(parentItem); serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(childItem, parent).thenReturn(); if (execParentJob) serializerMock(&Akonadi::SerializerInterface::filterDescendantItems).when(list, childItem).thenReturn(list); // WHEN auto associateJob = repository->associate(parent, child); if (execJob) associateJob->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()).exactly(1)); if (execJob) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(childItem, parent).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(parent).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(parentItem, repository.get()).exactly(1)); if (execParentJob) { if (parentItem.parentCollection().id() != childItem.parentCollection().id()) { QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection(), repository.get()).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob).exactly(1)); + QVERIFY(storageMock(&Akonadi::StorageInterface::createTransaction).when(repository.get()).thenReturn(transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob).exactly(1)); } else { QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()).exactly(1)); } } } } void shouldDissociateATaskFromItsProject_data() { QTest::addColumn("child"); QTest::addColumn("childItem"); QTest::addColumn("itemFetchJob"); QTest::addColumn("fetchJobFailed"); Domain::Task::Ptr taskChild(new Domain::Task); Akonadi::Item childItem(42); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("task nominal case") << taskChild << childItem << itemFetchJob << false; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); QTest::newRow("task job error with empty list") << taskChild << childItem << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("task job error with item") << taskChild << childItem << itemFetchJob << true; } void shouldDissociateATaskFromItsProject() { // GIVEN QFETCH(Domain::Task::Ptr, child); QFETCH(Akonadi::Item, childItem); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob); QFETCH(bool, fetchJobFailed); auto itemModifyJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the delete job storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()) .thenReturn(itemFetchJob); // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).thenReturn(); // WHEN repository->dissociate(child)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()).exactly(1)); if (!fetchJobFailed) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).exactly(1));; QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()).exactly(1)); } // Give a chance to job to delete themselves // in case of an error (since they use deleteLater() internally) QTest::qWait(10); } }; ZANSHIN_TEST_MAIN(AkonadiProjectRepositoryTest) #include "akonadiprojectrepositorytest.moc" diff --git a/tests/units/akonadi/akonaditaskrepositorytest.cpp b/tests/units/akonadi/akonaditaskrepositorytest.cpp index 0bd896c7..2e4e7d98 100644 --- a/tests/units/akonadi/akonaditaskrepositorytest.cpp +++ b/tests/units/akonadi/akonaditaskrepositorytest.cpp @@ -1,793 +1,793 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "utils/mockobject.h" #include "testlib/akonadifakedata.h" #include "testlib/akonadifakejobs.h" #include "testlib/akonadifakemonitor.h" #include "testlib/akonadifakestorage.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "akonadi/akonaditaskrepository.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistorageinterface.h" using namespace mockitopp; using namespace mockitopp::matcher; using namespace Testlib; Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) class AkonadiTaskRepositoryTest : public QObject { Q_OBJECT public: explicit AkonadiTaskRepositoryTest(QObject *parent = nullptr) : QObject(parent) { } private slots: void shouldCreateNewItems() { // GIVEN // A default collection for saving Akonadi::Collection col(42); // A task and its corresponding item not existing in storage yet Akonadi::Item item; Domain::Task::Ptr task(new Domain::Task); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::defaultCollection).when().thenReturn(col); storageMock(&Akonadi::StorageInterface::createItem).when(item, col) .thenReturn(itemCreateJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->create(task)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::defaultCollection).when().exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, col).exactly(1)); } void shouldCreateNewItemsInFirstWritableCollectionIsNothingInSettings() { // GIVEN // A few collections auto col1 = Akonadi::Collection(GenCollection().withId(42).withRootAsParent().withTaskContent()); col1.setRights(Akonadi::Collection::ReadOnly); auto col2 = Akonadi::Collection(GenCollection().withId(43).withRootAsParent().withTaskContent()); col2.setRights(Akonadi::Collection::CanCreateItem); auto col3 = Akonadi::Collection(GenCollection().withId(44).withRootAsParent().withTaskContent()); col3.setRights(Akonadi::Collection::CanCreateItem | Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem); auto collectionFetchJob = new Testlib::AkonadiFakeCollectionFetchJob; collectionFetchJob->setCollections(Akonadi::Collection::List() << col1 << col2 << col3); // A task and its corresponding item not existing in storage yet Akonadi::Item item; Domain::Task::Ptr task(new Domain::Task); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job and with no default collection Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::defaultCollection).when().thenReturn(Akonadi::Collection()); storageMock(&Akonadi::StorageInterface::fetchCollections).when(Akonadi::Collection::root(), Akonadi::StorageInterface::Recursive) .thenReturn(collectionFetchJob); storageMock(&Akonadi::StorageInterface::createItem).when(item, col3) .thenReturn(itemCreateJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->create(task)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::defaultCollection).when().exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, col3).exactly(1)); } void shouldEmitErrorIfNoFallbackCollectionIsFound() { // GIVEN // A few collections auto col1 = Akonadi::Collection(GenCollection().withId(42).withRootAsParent().withTaskContent()); col1.setRights(Akonadi::Collection::ReadOnly); auto col2 = Akonadi::Collection(GenCollection().withId(43).withRootAsParent().withTaskContent()); col2.setRights(Akonadi::Collection::CanCreateItem); auto col3 = Akonadi::Collection(GenCollection().withId(44).withRootAsParent().withTaskContent()); col3.setRights(Akonadi::Collection::ReadOnly); auto collectionFetchJob = new Testlib::AkonadiFakeCollectionFetchJob; collectionFetchJob->setCollections(Akonadi::Collection::List() << col1 << col2 << col3); // A task and its corresponding item not existing in storage yet Akonadi::Item item; Domain::Task::Ptr task(new Domain::Task); // Storage mock returning the create job and with no default collection Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::defaultCollection).when().thenReturn(Akonadi::Collection()); storageMock(&Akonadi::StorageInterface::fetchCollections).when(Akonadi::Collection::root(), Akonadi::StorageInterface::Recursive) .thenReturn(collectionFetchJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); auto job = repository->create(task); job->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::defaultCollection).when().exactly(1)); QVERIFY(job->error()); QVERIFY(!job->errorText().isEmpty()); } void shouldCreateNewChildrenInParentCollection() { // GIVEN // A parent item with a collection Akonadi::Collection col(42); Akonadi::Item parentItem(43); parentItem.setParentCollection(col); auto parent = Domain::Task::Ptr::create(); // A task and its corresponding item not existing in storage yet Akonadi::Item childItem; auto child = Domain::Task::Ptr::create(); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::createItem).when(childItem, col) .thenReturn(itemCreateJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(parent).thenReturn(parentItem); serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::updateItemParent).when(childItem, parent).thenReturn(); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->createChild(child, parent)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(parent).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::updateItemParent).when(childItem, parent).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(childItem, col).exactly(1)); } void shouldCreateNewItemsInProjectCollection() { // GIVEN // A project item with a collection Akonadi::Collection col(42); Akonadi::Item projectItem(43); projectItem.setParentCollection(col); auto project = Domain::Project::Ptr::create(); // A task and its corresponding item not existing in storage yet Akonadi::Item taskItem; auto task = Domain::Task::Ptr::create(); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::createItem).when(taskItem, col) .thenReturn(itemCreateJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(projectItem); serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(taskItem); serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(taskItem, project).thenReturn(); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->createInProject(task, project)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(taskItem, project).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(taskItem, col).exactly(1)); } void shouldCreateNewItemsInContext() { // GIVEN // a context item Akonadi::Item contextItem; contextItem.setId(42); // the context related to the item auto context = Domain::Context::Ptr::create(); // a default collection Akonadi::Collection defaultCollection(42); // A task and its corresponding item not existing in storage yet Akonadi::Item taskItem; auto task = Domain::Task::Ptr::create(); // A mock create job auto itemCreateJob = new FakeJob(this); // serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(taskItem); serializerMock(&Akonadi::SerializerInterface::addContextToTask).when(context, taskItem).thenReturn(); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::defaultCollection).when().thenReturn(defaultCollection); storageMock(&Akonadi::StorageInterface::createItem).when(taskItem, defaultCollection).thenReturn(itemCreateJob); // WHEN QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->createInContext(task, context)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::addContextToTask).when(context, taskItem).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::defaultCollection).when().exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(taskItem, defaultCollection).exactly(1)); } void shouldUpdateExistingItems() { // GIVEN // A default collection for saving Akonadi::Collection col(42); // A task and its corresponding item already existing in storage Akonadi::Item item(42); Domain::Task::Ptr task(new Domain::Task); // A mock create job auto itemModifyJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the create job storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()) .thenReturn(itemModifyJob); // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); // WHEN repository->update(task)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()).exactly(1)); } void shouldRemoveATask_data() { QTest::addColumn("item"); QTest::addColumn("itemFetchJob1"); QTest::addColumn("itemFetchJob2"); QTest::addColumn("list"); QTest::addColumn("itemFetchJobSucceeded"); QTest::addColumn("collectionItemsFetchJobSucceeded"); Akonadi::Collection col(40); Akonadi::Item item(42); item.setParentCollection(col); Akonadi::Item item2(43); auto itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << item); auto itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item::List list; QTest::newRow("nominal case") << item << itemFetchJob1 << itemFetchJob2 << list << true << true; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); QTest::newRow("item job error with empty list") << item << itemFetchJob1 << itemFetchJob2 << list << false << false; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); itemFetchJob1->setItems(Akonadi::Item::List() << item); QTest::newRow("item job error with item") << item << itemFetchJob1 << itemFetchJob2 << list << false << false; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << item); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); QTest::newRow("items job error with empty list") << item << itemFetchJob1 << itemFetchJob2 << list << true << false; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << item); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); list << item2; itemFetchJob2->setItems(list); QTest::newRow("remove item and his child") << item << itemFetchJob1 << itemFetchJob2 << list << true << true; } void shouldRemoveATask() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob1); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob2); QFETCH(Akonadi::Item::List, list); QFETCH(bool, itemFetchJobSucceeded); QFETCH(bool, collectionItemsFetchJobSucceeded); Domain::Task::Ptr task(new Domain::Task); Akonadi::Item::List removedList; removedList << list << item; // A mock delete job auto itemDeleteJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the delete job storageMock(&Akonadi::StorageInterface::fetchItem).when(item, repository.get()) .thenReturn(itemFetchJob1); storageMock(&Akonadi::StorageInterface::fetchItems).when(item.parentCollection(), repository.get()) .thenReturn(itemFetchJob2); storageMock(&Akonadi::StorageInterface::removeItems).when(removedList, repository.get()) .thenReturn(itemDeleteJob); // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); serializerMock(&Akonadi::SerializerInterface::filterDescendantItems).when(list, item).thenReturn(list); // WHEN repository->remove(task)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item, repository.get()).exactly(1)); if (itemFetchJobSucceeded) { QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItems).when(item.parentCollection(), repository.get()).exactly(1)); if (collectionItemsFetchJobSucceeded) { QVERIFY(storageMock(&Akonadi::StorageInterface::removeItems).when(removedList, repository.get()).exactly(1)); } } } void shouldPromoteTaskToProject() { // GIVEN // A default collection for saving Akonadi::Collection col(42); // A task and its corresponding item already existing in storage Akonadi::Item item(42); Domain::Task::Ptr task(new Domain::Task); // A mock fetch job auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << item); // A mock modify job auto itemModifyJob = new FakeJob(this); Utils::MockObject serializerMock; Utils::MockObject storageMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Serializer mock returning the item for the task and transforming it into a project serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).thenReturn(item); serializerMock(&Akonadi::SerializerInterface::promoteItemToProject).when(item).thenReturn(); // Storage mock returning the modify job storageMock(&Akonadi::StorageInterface::fetchItem).when(item, repository.get()) .thenReturn(itemFetchJob); storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()) .thenReturn(itemModifyJob); // WHEN repository->promoteToProject(task)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::promoteItemToProject).when(item).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item, repository.get()).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, repository.get()).exactly(1)); } void shouldAssociateATaskToAnother_data() { QTest::addColumn("childItem"); QTest::addColumn("parentItem"); QTest::addColumn("child"); QTest::addColumn("parent"); QTest::addColumn("itemFetchJob1"); QTest::addColumn("itemFetchJob2"); QTest::addColumn("itemFetchJob3"); QTest::addColumn("execJob"); QTest::addColumn("execParentJob"); QTest::addColumn("list"); Akonadi::Collection col(40); Akonadi::Item childItem(42); childItem.setParentCollection(col); Domain::Task::Ptr child(new Domain::Task); Akonadi::Item parentItem(41); parentItem.setParentCollection(col); Domain::Task::Ptr parent(new Domain::Task); auto itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); auto itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << childItem << parentItem); auto itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item::List list; QTest::newRow("nominal case") << childItem << parentItem << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); QTest::newRow("child job error with empty list") << childItem << parentItem << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); QTest::newRow("child job error with item") << childItem << parentItem << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); QTest::newRow("parent job error with empty list") << childItem << parentItem << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); itemFetchJob2->setItems(Akonadi::Item::List() << childItem << parentItem); QTest::newRow("parent job error with item") << childItem << parentItem << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Collection col2(39); Akonadi::Item parentItem2(41); parentItem2.setParentCollection(col2); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); QTest::newRow("update and move item") << childItem << parentItem2 << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item childItem2(43); Akonadi::Item::List list2; list2 << childItem2; itemFetchJob3->setItems(list2); QTest::newRow("update and move item and his child") << childItem << parentItem2 << child << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list2; } void shouldAssociateATaskToAnother() { // GIVEN QFETCH(Akonadi::Item, childItem); QFETCH(Akonadi::Item, parentItem); QFETCH(Domain::Task::Ptr, child); QFETCH(Domain::Task::Ptr, parent); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob1); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob2); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob3); QFETCH(bool, execJob); QFETCH(bool, execParentJob); QFETCH(Akonadi::Item::List, list); // A mock create job auto itemModifyJob = new FakeJob(this); auto transactionJob = new FakeJob(this); auto itemsMoveJob = new FakeJob(this); Akonadi::Item::List movedList; movedList << childItem << list; Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the create job storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()) .thenReturn(itemFetchJob1); storageMock(&Akonadi::StorageInterface::fetchItems).when(parentItem.parentCollection(), repository.get()) .thenReturn(itemFetchJob2); if (parentItem.parentCollection().id() != childItem.parentCollection().id()) { storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection(), repository.get()) .thenReturn(itemFetchJob3); - storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob); + storageMock(&Akonadi::StorageInterface::createTransaction).when(repository.get()).thenReturn(transactionJob); storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob) .thenReturn(itemsMoveJob); } else { storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()) .thenReturn(itemModifyJob); } // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(parent).thenReturn(parentItem); serializerMock(&Akonadi::SerializerInterface::createTaskFromItem).when(childItem).thenReturn(child); serializerMock(&Akonadi::SerializerInterface::createTaskFromItem).when(parentItem).thenReturn(parent); serializerMock(&Akonadi::SerializerInterface::updateItemParent).when(childItem, parent).thenReturn(); serializerMock(&Akonadi::SerializerInterface::itemUid).when(parentItem).thenReturn(QStringLiteral("parent")); serializerMock(&Akonadi::SerializerInterface::itemUid).when(childItem).thenReturn(QStringLiteral("child")); serializerMock(&Akonadi::SerializerInterface::relatedUidFromItem).when(parentItem).thenReturn(QString()); serializerMock(&Akonadi::SerializerInterface::relatedUidFromItem).when(childItem).thenReturn(QString()); if (execParentJob) serializerMock(&Akonadi::SerializerInterface::filterDescendantItems).when(list, childItem).thenReturn(list); // WHEN auto associateJob = repository->associate(parent, child); if (execJob) associateJob->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()).exactly(1)); if (execJob) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::updateItemParent).when(childItem, parent).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(parent).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItems).when(parentItem.parentCollection(), repository.get()).exactly(1)); if (execParentJob) { if (parentItem.parentCollection().id() == childItem.parentCollection().id()) QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()).exactly(1)); else { //QVERIFY(serializerMock(&Akonadi::SerializerInterface::filterDescendantItems).when(list, childItem).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection(), repository.get()).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob).exactly(1)); + QVERIFY(storageMock(&Akonadi::StorageInterface::createTransaction).when(repository.get()).thenReturn(transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob).exactly(1)); } } } } void shouldPreventCyclesDuringAssociation() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto task42 = serializer->createTaskFromItem(data.item(42)); auto task44 = serializer->createTaskFromItem(data.item(44)); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer repository(new Akonadi::TaskRepository(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer)); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); // WHEN auto job = repository->associate(task44, task42); QVERIFY(!job->exec()); // THEN QVERIFY(spy.isEmpty()); QVERIFY(job->error() != KJob::NoError); QVERIFY(!job->errorText().isEmpty()); } void shouldDissociateATaskFromItsParent_data() { QTest::addColumn("child"); QTest::addColumn("childItem"); QTest::addColumn("itemFetchJob"); QTest::addColumn("childJobFailed"); Domain::Task::Ptr child(new Domain::Task); Akonadi::Item childItem(42); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("nominal case") << child << childItem << itemFetchJob << false; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); QTest::newRow("child job error with empty list") << child << childItem << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("child job error with item") << child << childItem << itemFetchJob << true; } void shouldDissociateATaskFromItsParent() { // GIVEN QFETCH(Domain::Task::Ptr, child); QFETCH(Akonadi::Item, childItem); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob); QFETCH(bool, childJobFailed); auto itemModifyJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the delete job storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()) .thenReturn(itemFetchJob); // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).thenReturn(); // WHEN repository->dissociate(child)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()).exactly(1)); if (!childJobFailed) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).exactly(1));; QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()).exactly(1)); } } void shouldDissociateAllLinksOfTask_data() { shouldDissociateATaskFromItsParent_data(); } void shouldDissociateAllLinksOfTask() { // GIVEN QFETCH(Domain::Task::Ptr, child); QFETCH(Akonadi::Item, childItem); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob); QFETCH(bool, childJobFailed); auto itemModifyJob = new FakeJob(this); Utils::MockObject storageMock; Utils::MockObject serializerMock; QScopedPointer repository(new Akonadi::TaskRepository(storageMock.getInstance(), serializerMock.getInstance())); // Storage mock returning the delete job storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()) .thenReturn(itemFetchJob); // Serializer mock returning the item for the task serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).thenReturn(); serializerMock(&Akonadi::SerializerInterface::clearItem).when(any()).thenReturn(); // WHEN repository->dissociateAll(child)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem, repository.get()).exactly(1)); if (!childJobFailed) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::clearItem).when(any()).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, repository.get()).exactly(1)); } // Give a chance to job to delete themselves // in case of an error (since they use deleteLater() internally) QTest::qWait(10); } }; ZANSHIN_TEST_MAIN(AkonadiTaskRepositoryTest) #include "akonaditaskrepositorytest.moc"