diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt index 052f4af6..3c625e83 100644 --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -1,44 +1,46 @@ set(akonadi_SRCS akonadiapplicationselectedattribute.cpp akonadicache.cpp akonadicachingstorage.cpp akonadicollectionfetchjobinterface.cpp akonadiconfigdialog.cpp akonadicontextqueries.cpp akonadicontextrepository.cpp akonadidatasourcequeries.cpp akonadidatasourcerepository.cpp akonadiitemfetchjobinterface.cpp akonadilivequeryhelpers.cpp akonadilivequeryintegrator.cpp akonadimessaging.cpp akonadimessaginginterface.cpp akonadimonitorimpl.cpp akonadimonitorinterface.cpp akonadinotequeries.cpp akonadinoterepository.cpp akonadiprojectqueries.cpp akonadiprojectrepository.cpp akonadiserializer.cpp akonadiserializerinterface.cpp akonadistorage.cpp akonadistorageinterface.cpp akonadistoragesettings.cpp akonaditagfetchjobinterface.cpp akonaditagqueries.cpp akonaditagrepository.cpp akonaditaskqueries.cpp akonaditaskrepository.cpp akonaditimestampattribute.cpp ) add_library(akonadi STATIC ${akonadi_SRCS}) target_link_libraries(akonadi KF5::AkonadiCalendar KF5::AkonadiCore KF5::AkonadiNotes KF5::AkonadiWidgets KF5::Mime KF5::CalendarCore KF5::IdentityManagement + KF5::ConfigCore + KF5::KDELibs4Support ) diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index 45be9687..1ef19fd9 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,683 +1,684 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit 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 "akonadiserializer.h" #include #include #include #include #include #include #include +#include #include #include "utils/mem_fn.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } bool Serializer::representsAkonadiTag(Domain::Tag::Ptr tag, Tag akonadiTag) const { return tag->property("tagId").toLongLong() == akonadiTag.id(); } QString Serializer::objectUid(SerializerInterface::QObjectPtr object) { return object->property("todoUid").toString(); } QString Serializer::itemUid(const Item &item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->uid(); } else { return QString(); } } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + " » " + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(NoteUtils::noteMimeType())) types |= Domain::DataSource::Notes; if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } dataSource->setProperty("collectionId", collection.id()); } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { const auto id = dataSource->property("collectionId").value(); auto collection = Collection(id); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); return collection; } bool Serializer::isSelectedCollection(Collection collection) { if (!isNoteCollection(collection) && !isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isNoteCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(NoteUtils::noteMimeType()); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return todo->customProperty("Zanshin", "Project").isEmpty(); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setDone(todo->isCompleted()); task->setDoneDate(todo->completed().dateTime().toUTC()); task->setStartDate(todo->dtStart().dateTime().toUTC()); task->setDueDate(todo->dtDue().dateTime().toUTC()); task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setRunning(todo->customProperty("Zanshin", "Running") == QLatin1String("1")); switch (todo->recurrence()->recurrenceType()) { case KCalCore::Recurrence::rDaily: task->setRecurrence(Domain::Task::RecursDaily); break; case KCalCore::Recurrence::rWeekly: task->setRecurrence(Domain::Task::RecursWeekly); break; case KCalCore::Recurrence::rMonthlyDay: task->setRecurrence(Domain::Task::RecursMonthly); break; default: // Other cases are not supported for now and as such just ignored break; } QMimeDatabase mimeDb; const auto attachmentsInput = todo->attachments(); Domain::Task::Attachments attachments; attachments.reserve(attachmentsInput.size()); std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(), std::back_inserter(attachments), [&mimeDb] (const KCalCore::Attachment::Ptr &attach) { Domain::Task::Attachment attachment; if (attach->isUri()) attachment.setUri(QUrl(attach->uri())); else attachment.setData(attach->decodedData()); attachment.setLabel(attach->label()); attachment.setMimeType(attach->mimeType()); attachment.setIconName(mimeDb.mimeTypeForName(attach->mimeType()).iconName()); return attachment; }); task->setAttachments(attachments); if (todo->attendeeCount() > 0) { const auto attendees = todo->attendees(); const auto delegate = std::find_if(attendees.begin(), attendees.end(), [] (const KCalCore::Attendee::Ptr &attendee) { return attendee->status() == KCalCore::Attendee::Accepted; }); if (delegate != attendees.end()) { task->setDelegate(Domain::Task::Delegate((*delegate)->name(), (*delegate)->email())); } } } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); todo->setDtStart(KDateTime(task->startDate(), KDateTime::UTC)); todo->setDtDue(KDateTime(task->dueDate(), KDateTime::UTC)); if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } switch (task->recurrence()) { case Domain::Task::NoRecurrence: break; case Domain::Task::RecursDaily: todo->recurrence()->setDaily(1); break; case Domain::Task::RecursWeekly: todo->recurrence()->setWeekly(1); break; case Domain::Task::RecursMonthly: todo->recurrence()->setMonthly(1); break; } for (const auto &attachment : task->attachments()) { KCalCore::Attachment::Ptr attach(new KCalCore::Attachment(QByteArray())); if (attachment.isUri()) attach->setUri(attachment.uri().toString()); else attach->setDecodedData(attachment.data()); attach->setMimeType(attachment.mimeType()); attach->setLabel(attachment.label()); todo->addAttachment(attach); } if (task->delegate().isValid()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(task->delegate().name(), task->delegate().email(), true, KCalCore::Attendee::Accepted)); todo->addAttendee(attendee); } if (task->isRunning()) { todo->setCustomProperty("Zanshin", "Running", "1"); } else { todo->removeCustomProperty("Zanshin", "Running"); } // Needs to be done after all other dates are positioned // since this applies the recurrence logic if (task->isDone()) todo->setCompleted(KDateTime(task->doneDate())); else todo->setCompleted(false); Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } if (task->property("parentCollectionId").isValid()) { auto parentId = task->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else if (isNoteItem(item)) { const auto message = item.payload(); const auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid"); return relatedHeader ? relatedHeader->asUnicodeString() : QString(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } else if (isNoteItem(item)) { auto note = item.payload(); note->removeHeader("X-Zanshin-RelatedProjectUid"); const QByteArray parentUid = project->property("todoUid").toString().toUtf8(); if (!parentUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(parentUid); note->appendHeader(relatedHeader); } note->assemble(); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } void Serializer::promoteItemToProject(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); } void Serializer::clearItem(Akonadi::Item *item) { Q_ASSERT(item); if (!isTaskItem(*item)) return; // NOTE : Currently not working, when akonadistorage test will make it pass, we will use it // item->clearTags(); foreach (const Tag& tag, item->tags()) item->clearTag(tag); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound)); std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isNoteItem(Item item) { return item.hasPayload(); } Domain::Note::Ptr Serializer::createNoteFromItem(Akonadi::Item item) { if (!isNoteItem(item)) return Domain::Note::Ptr(); Domain::Note::Ptr note = Domain::Note::Ptr::create(); updateNoteFromItem(note, item); return note; } void Serializer::updateNoteFromItem(Domain::Note::Ptr note, Item item) { if (!isNoteItem(item)) return; auto message = item.payload(); note->setTitle(message->subject(true)->asUnicodeString()); note->setText(message->mainBodyPart()->decodedText()); note->setProperty("itemId", item.id()); if (auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid")) { note->setProperty("relatedUid", relatedHeader->asUnicodeString()); } else { note->setProperty("relatedUid", QVariant()); } } Item Serializer::createItemFromNote(Domain::Note::Ptr note) { NoteUtils::NoteMessageWrapper builder; builder.setTitle(note->title()); builder.setText(note->text() + '\n'); // Adding an extra '\n' because KMime always removes it... KMime::Message::Ptr message = builder.message(); if (!note->property("relatedUid").toString().isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(note->property("relatedUid").toString().toUtf8()); message->appendHeader(relatedHeader); } Akonadi::Item item; if (note->property("itemId").isValid()) { item.setId(note->property("itemId").value()); } item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); return item; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; return !isTaskItem(item); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::hasContextTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isContext), this, _1)); } bool Serializer::hasAkonadiTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isAkonadiTag), this, _1)); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isAkonadiTag(const Tag &tag) const { return tag.type() == Akonadi::Tag::PLAIN; } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("tagId").isValid()) return false; auto tagId = context->property("tagId").value(); Akonadi::Tag tag(tagId); return item.hasTag(tag); } Domain::Tag::Ptr Serializer::createTagFromAkonadiTag(Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return Domain::Tag::Ptr(); auto tag = Domain::Tag::Ptr::create(); updateTagFromAkonadiTag(tag, akonadiTag); return tag; } void Serializer::updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return; tag->setProperty("tagId", akonadiTag.id()); tag->setName(akonadiTag.name()); } Akonadi::Tag Serializer::createAkonadiTagFromTag(Domain::Tag::Ptr tag) { auto akonadiTag = Akonadi::Tag(); akonadiTag.setName(tag->name()); akonadiTag.setType(Akonadi::Tag::PLAIN); akonadiTag.setGid(QByteArray(tag->name().toLatin1())); const auto tagProperty = tag->property("tagId"); if (tagProperty.isValid()) akonadiTag.setId(tagProperty.value()); return akonadiTag; } bool Serializer::isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) { if (!tag->property("tagId").isValid()) return false; auto tagId = tag->property("tagId").value(); Akonadi::Tag akonadiTag(tagId); return item.hasTag(akonadiTag); } diff --git a/src/akonadi/akonadistoragesettings.cpp b/src/akonadi/akonadistoragesettings.cpp index eaa362f6..4fae089b 100644 --- a/src/akonadi/akonadistoragesettings.cpp +++ b/src/akonadi/akonadistoragesettings.cpp @@ -1,76 +1,77 @@ /* This file is part of Zanshin Copyright 2012 Christian Mollekopf 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 "akonadistoragesettings.h" #include #include +#include using namespace Akonadi; StorageSettings::StorageSettings() : QObject() { } StorageSettings &StorageSettings::instance() { static StorageSettings i; return i; } Collection StorageSettings::defaultNoteCollection() { KConfigGroup config(KSharedConfig::openConfig(), "General"); Collection::Id id = config.readEntry("defaultNoteCollection", -1); return Collection(id); } Collection StorageSettings::defaultTaskCollection() { KConfigGroup config(KSharedConfig::openConfig(), "General"); Collection::Id id = config.readEntry("defaultCollection", -1); return Collection(id); } void StorageSettings::setDefaultNoteCollection(const Collection &collection) { if (defaultNoteCollection() == collection) return; KConfigGroup config(KSharedConfig::openConfig(), "General"); config.writeEntry("defaultNoteCollection", QString::number(collection.id())); config.sync(); emit defaultNoteCollectionChanged(collection); } void StorageSettings::setDefaultTaskCollection(const Collection &collection) { if (defaultTaskCollection() == collection) return; KConfigGroup config(KSharedConfig::openConfig(), "General"); config.writeEntry("defaultCollection", QString::number(collection.id())); config.sync(); emit defaultTaskCollectionChanged(collection); } diff --git a/tests/testlib/CMakeLists.txt b/tests/testlib/CMakeLists.txt index 05b8a244..2c1dea5e 100644 --- a/tests/testlib/CMakeLists.txt +++ b/tests/testlib/CMakeLists.txt @@ -1,30 +1,31 @@ set(testlib_SRCS akonadidebug.cpp akonadifakedata.cpp akonadifakedataxmlloader.cpp akonadifakejobs.cpp akonadifakemonitor.cpp akonadifakestorage.cpp akonadifakestoragebehavior.cpp akonadistoragetestbase.cpp fakejob.cpp gencollection.cpp gennote.cpp gentag.cpp gentodo.cpp modeltest.cpp monitorspy.cpp testhelpers.cpp ) include_directories(${CMAKE_SOURCE_DIR}/tests ${CMAKE_SOURCE_DIR}/src) add_library(testlib STATIC ${testlib_SRCS}) target_link_libraries(testlib KF5::AkonadiCore KF5::AkonadiNotes KF5::AkonadiXml KF5::CalendarCore KF5::Mime Qt5::Test + KF5::KDELibs4Support ) diff --git a/tests/testlib/akonadistoragetestbase.cpp b/tests/testlib/akonadistoragetestbase.cpp index 2ab0661a..6ec09ac5 100644 --- a/tests/testlib/akonadistoragetestbase.cpp +++ b/tests/testlib/akonadistoragetestbase.cpp @@ -1,1254 +1,1255 @@ /* 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 #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/akonaditagfetchjobinterface.h" #include "akonadi/akonaditimestampattribute.h" using namespace Testlib; AkonadiStorageTestBase::AkonadiStorageTestBase(QObject *parent) : QObject(parent) { qRegisterMetaType(); 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::addColumn("contentTypes"); QTest::newRow("all") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!", "Notes" }) << Akonadi::Storage::Recursive << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks); QTest::newRow("notes") << Akonadi::Collection::root() << QStringList({ "Notes" }) << Akonadi::Storage::Recursive << int(Akonadi::StorageInterface::Notes); QTest::newRow("tasks") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!" }) << Akonadi::Storage::Recursive << int(Akonadi::StorageInterface::Tasks); QTest::newRow("base type") << calendar2() << QStringList({"Calendar2"}) << Akonadi::Storage::Base << int(Akonadi::StorageInterface::Tasks); QTest::newRow("firstLevel type") << calendar1() << QStringList({"Calendar2"}) << Akonadi::Storage::FirstLevel << int(Akonadi::StorageInterface::Tasks); QTest::newRow("recursive type") << calendar1() << QStringList({"Calendar2", "Calendar3"}) << Akonadi::Storage::Recursive << int(Akonadi::StorageInterface::Tasks); } void AkonadiStorageTestBase::shouldListCollections() { // GIVEN QFETCH(Akonadi::Collection, collection); QFETCH(QStringList, expectedNames); QFETCH(Akonadi::StorageInterface::FetchDepth, depth); QFETCH(int, contentTypes); auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(collection, depth, Akonadi::StorageInterface::FetchContentTypes(contentTypes)); 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, Akonadi::Storage::Tasks|Akonadi::Storage::Notes); 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 = { "{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}" }; // WHEN auto job = storage->fetchItems(calendar2()); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); Akonadi::Tag::List tags = item.tags(); QVERIFY(!item.tags().isEmpty()); foreach (const auto &tag, tags) { QVERIFY(tag.isValid()); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldListTags() { // GIVEN auto storage = createStorage(); const QStringList expectedGids = { "change-me", "delete-me", "errands-context", "online-context", "philosophy-tag", "physics-tag" }; // WHEN auto job = storage->fetchTags(); AKVERIFYEXEC(job->kjob()); // THEN auto tags = job->tags(); QStringList tagGids; tagGids.reserve(tags.size()); foreach (const auto &tag, tags) { tagGids << tag.gid(); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } tagGids.sort(); QCOMPARE(tagGids, expectedGids); } void AkonadiStorageTestBase::shouldListItemsAssociatedWithTag() { // GIVEN auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); const QStringList expectedRemoteIds = { "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{7824df00-2fd6-47a4-8319-52659dc82005}" }; // WHEN auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); 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(); } } 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); 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); 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 colection 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); 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(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(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()); const Akonadi::Collection notesCol = fetchCollectionByRID(QStringLiteral("{f5e3f1be-b998-4c56-aa3d-e3a6e7e5493a}")); Akonadi::Item item = fetchItemByRID(QStringLiteral("{d0159c99-0d23-41fa-bb5f-436570140f8b}"), notesCol); QVERIFY(item.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeItem(item); 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(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(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); 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::shouldNotifyItemTagAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}"), calendar2()); QVERIFY(item.isValid()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); // An existing tag (if we trust the test data) Akonadi::Tag tag(5); // WHEN item.setTag(tag); auto storage = createStorage(); auto job = storage->updateItem(item); 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()); QVERIFY(notifiedItem.hasPayload()); Akonadi::Tag::List notifiedTags = notifiedItem.tags(); QVERIFY(notifiedTags.contains(tag)); foreach (const auto &tag, notifiedTags) { QVERIFY(tag.isValid()); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemTagRemoved() // aka dissociate { // GIVEN auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("philosophy-tag")); const QString expectedRemoteIds = {QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82006}")}; auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); auto item = job->items().at(0); QCOMPARE(item.remoteId(), expectedRemoteIds); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); QVERIFY(!item.tags().isEmpty()); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN item.clearTag(tag); auto jobUpdate = storage->updateItem(item); AKVERIFYEXEC(jobUpdate); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QVERIFY(!notifiedItem.tags().contains(tag)); } void AkonadiStorageTestBase::shouldNotifyTagAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); MonitorSpy monitorSpy(monitor.data()); // A tag Akonadi::Tag tag; tag.setGid("gid"); tag.setName(QStringLiteral("name")); tag.setType("type"); // WHEN auto storage = createStorage(); auto job = storage->createTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.gid(), tag.gid()); QCOMPARE(notifiedTag.name(), tag.name()); QCOMPARE(notifiedTag.type(), tag.type()); } void AkonadiStorageTestBase::shouldNotifyTagRemoved() { // GIVEN // An existing tag (if we trust the test data) connected to an existing item tagged to it auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("delete-me")); // NOTE : this item was linked to the delete-me tag during test time const QString expectedRemoteIds = {QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}")}; auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); auto itemTagged = job->items().at(0); QCOMPARE(itemTagged.remoteId(), expectedRemoteIds); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); QSignalSpy spyItemChanged(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto jobDelete = storage->removeTag(tag); AKVERIFYEXEC(jobDelete); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); QTRY_VERIFY(!spyItemChanged.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); QCOMPARE(spyItemChanged.size(), 1); auto notifiedItem = spyItemChanged.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), itemTagged.id()); } void AkonadiStorageTestBase::shouldNotifyTagChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); MonitorSpy monitorSpy(monitor.data()); // An existing tag (if we trust the test data) Akonadi::Tag tag(6); tag.setName(QStringLiteral("Oh it changed!")); // WHEN auto storage = createStorage(); auto job = storage->updateTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); QCOMPARE(notifiedTag.name(), tag.name()); } void AkonadiStorageTestBase::shouldReadDefaultNoteCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultNoteCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultNoteCollection(), Akonadi::Collection(24)); } void AkonadiStorageTestBase::shouldReadDefaultTaskCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultTaskCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultTaskCollection(), 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); 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()); // KCalCore 4.83 fixes this bug #if KCALCORE_VERSION < 0x045300 QCOMPARE(notifiedItem.payload()->uid(), todo->uid()); QCOMPARE(notifiedItem.payload()->summary(), todo->summary()); QCOMPARE(notifiedItem.payload()->description(), todo->description()); QEXPECT_FAIL("", "Bug introduced by 76c686bc1de3a5d16956a627744ce352bc28d12a in KCalCore", Continue); QCOMPARE(*notifiedItem.payload(), *todo); QEXPECT_FAIL("", "Bug introduced by 76c686bc1de3a5d16956a627744ce352bc28d12a in KCalCore", Continue); QCOMPARE(notifiedItem.payload()->status(), todo->status()); #else QCOMPARE(*notifiedItem.payload(), *todo); #endif } 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(); 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); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item1 = job->items().at(0); job = storage->fetchItem(item2); 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(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(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); 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()); 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()); 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); 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); 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::shouldCreateTag() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); MonitorSpy monitorSpy(monitor.data()); // A tag Akonadi::Tag tag; QString name = QStringLiteral("Tag42"); const QByteArray type = QByteArray("Zanshin-Context"); const QByteArray gid = QByteArray(name.toLatin1()); tag.setName(name); tag.setType(QByteArray("Zanshin-Context")); tag.setGid(gid); // WHEN auto job = storage->createTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.name(), name); QCOMPARE(notifiedTag.type(), type); QCOMPARE(notifiedTag.gid(), gid); } void AkonadiStorageTestBase::shouldRemoveTag() { // GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing tag Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); // WHEN auto job = storage->removeTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); } void AkonadiStorageTestBase::shouldUpdateTag() { // GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); MonitorSpy monitorSpy(monitor.data()); // An existing tag Akonadi::Tag tag = fetchTagByGID(QStringLiteral("change-me")); // WHEN auto job = storage->updateTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.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); 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); 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); 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()); } void AkonadiStorageTestBase::shouldNotNotifyCollectionSelectionChangesForIrrelevantCollections() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = emails(); // 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); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QVERIFY(selectionSpy.isEmpty()); } Akonadi::Item AkonadiStorageTestBase::fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection) { Akonadi::Item item; item.setRemoteId(remoteId); auto job = createStorage()->fetchItem(item); 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, Akonadi::StorageInterface::AllContent); 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::Tag AkonadiStorageTestBase::fetchTagByGID(const QString &gid) { auto job = createStorage()->fetchTags(); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Tag(); } auto tags = job->tags(); foreach (const auto &tag, tags) { if (tag.gid() == gid) return tag; } return Akonadi::Tag(); } 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/testlib/gentodo.cpp b/tests/testlib/gentodo.cpp index 238ab768..884e8598 100644 --- a/tests/testlib/gentodo.cpp +++ b/tests/testlib/gentodo.cpp @@ -1,157 +1,158 @@ /* 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 "gentodo.h" #include #include +#include using namespace Testlib; GenTodo::GenTodo(const Akonadi::Item &item) : m_item(item) { m_item.setMimeType(KCalCore::Todo::todoMimeType()); if (!m_item.hasPayload()) m_item.setPayload(KCalCore::Todo::Ptr::create()); } Testlib::GenTodo::operator Akonadi::Item() { return m_item; } GenTodo &GenTodo::withId(Akonadi::Item::Id id) { m_item.setId(id); return *this; } GenTodo &GenTodo::withParent(Akonadi::Collection::Id id) { m_item.setParentCollection(Akonadi::Collection(id)); return *this; } GenTodo &GenTodo::withTags(const QList &ids) { auto tags = Akonadi::Tag::List(); std::transform(ids.constBegin(), ids.constEnd(), std::back_inserter(tags), [] (Akonadi::Tag::Id id) { return Akonadi::Tag(id); }); m_item.setTags(tags); return *this; } GenTodo &GenTodo::asProject(bool value) { auto todo = m_item.payload(); if (value) todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); else todo->removeCustomProperty("Zanshin", "Project"); return *this; } GenTodo &GenTodo::withUid(const QString &uid) { m_item.payload()->setUid(uid); return *this; } GenTodo &GenTodo::withParentUid(const QString &uid) { m_item.payload()->setRelatedTo(uid); return *this; } GenTodo &GenTodo::withTitle(const QString &title) { m_item.payload()->setSummary(title); return *this; } GenTodo &GenTodo::withText(const QString &text) { m_item.payload()->setDescription(text); return *this; } GenTodo &GenTodo::done(bool value) { m_item.payload()->setCompleted(value); return *this; } GenTodo &GenTodo::withDoneDate(const QString &date) { m_item.payload()->setCompleted(KDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withDoneDate(const QDateTime &date) { m_item.payload()->setCompleted(KDateTime(date)); return *this; } GenTodo &GenTodo::withStartDate(const QString &date) { m_item.payload()->setDtStart(KDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withStartDate(const QDateTime &date) { m_item.payload()->setDtStart(KDateTime(date)); return *this; } GenTodo &GenTodo::withDueDate(const QString &date) { m_item.payload()->setDtDue(KDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withDueDate(const QDateTime &date) { m_item.payload()->setDtDue(KDateTime(date)); return *this; } GenTodo &GenTodo::withDelegate(const QString &name, const QString &email) { withNoDelegate(); KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(name, email, true, KCalCore::Attendee::Delegated)); m_item.payload()->addAttendee(attendee); return *this; } GenTodo &GenTodo::withNoDelegate() { m_item.payload()->clearAttendees(); return *this; }