File indexing completed on 2025-01-05 04:59:51

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
0003  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 
0007 #include <testlib/qtest_zanshin.h>
0008 
0009 #include "akonadi/akonadidatasourcequeries.h"
0010 #include "akonadi/akonadiserializer.h"
0011 #include "akonadi/akonadistoragesettings.h"
0012 
0013 #include "utils/jobhandler.h"
0014 #include "utils/mem_fn.h"
0015 
0016 #include "testlib/akonadifakedata.h"
0017 #include "testlib/gencollection.h"
0018 #include "testlib/gentodo.h"
0019 #include "testlib/testhelpers.h"
0020 
0021 using namespace Testlib;
0022 
0023 typedef std::function<Domain::QueryResult<Domain::DataSource::Ptr>::Ptr(Domain::DataSourceQueries*)> QueryFunction;
0024 Q_DECLARE_METATYPE(QueryFunction)
0025 typedef std::function<void(Akonadi::StorageSettings *, const Akonadi::Collection &)> SetDefaultCollectionFunction;
0026 Q_DECLARE_METATYPE(SetDefaultCollectionFunction)
0027 typedef std::function<Akonadi::Collection(Akonadi::StorageSettings *)> GetDefaultCollectionFunction;
0028 Q_DECLARE_METATYPE(GetDefaultCollectionFunction)
0029 
0030 class AkonadiDataSourceQueriesTest : public QObject
0031 {
0032     Q_OBJECT
0033 public:
0034     explicit AkonadiDataSourceQueriesTest(QObject *parent = nullptr)
0035         : QObject(parent)
0036     {
0037         qRegisterMetaType<QueryFunction>();
0038     }
0039 
0040 private slots:
0041     void shouldCheckIfASourceIsDefaultFromSettings()
0042     {
0043         // GIVEN
0044         const auto minId = qint64(42);
0045         const auto maxId = qint64(44);
0046         const auto defaultId = qint64(43);
0047 
0048         // A default collection for saving
0049         Akonadi::StorageSettings::instance().setDefaultCollection(Akonadi::Collection(defaultId));
0050 
0051         // A few data sources
0052         AkonadiFakeData data;
0053         for (auto id = minId; id <= maxId; ++id) {
0054             auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent();
0055             data.createCollection(col.withTaskContent());
0056         }
0057 
0058         // WHEN
0059         auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer);
0060         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0061                                                                                          serializer,
0062                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0063 
0064         // THEN
0065         for (auto id = minId; id <= maxId; ++id) {
0066             auto source = serializer->createDataSourceFromCollection(data.collection(id), Akonadi::SerializerInterface::BaseName);
0067             QCOMPARE(queries->isDefaultSource(source), id == defaultId);
0068         }
0069     }
0070 
0071     void shouldStoreDefaultSourceInTheSettingsAndNotify()
0072     {
0073         // GIVEN
0074         const auto minId = qint64(42);
0075         const auto maxId = qint64(44);
0076         const auto defaultId = qint64(43);
0077 
0078         // A default collection for saving
0079         Akonadi::StorageSettings::instance().setDefaultCollection(Akonadi::Collection(minId));
0080 
0081         // A few data sources
0082         AkonadiFakeData data;
0083         for (auto id = minId; id <= maxId; ++id) {
0084             auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent();
0085             data.createCollection(col.withTaskContent());
0086         }
0087 
0088         // WHEN
0089         auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer);
0090         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0091                                                                                          serializer,
0092                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0093         QSignalSpy spy(queries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged);
0094         auto defaultSource = serializer->createDataSourceFromCollection(data.collection(defaultId), Akonadi::SerializerInterface::BaseName);
0095         queries->setDefaultSource(defaultSource);
0096 
0097         // THEN
0098         QCOMPARE(Akonadi::StorageSettings::instance().defaultCollection().id(), defaultId);
0099         QCOMPARE(spy.count(), 1);
0100     }
0101 
0102     void shouldLookInAllReportedForTopLevelSources()
0103     {
0104         // GIVEN
0105         AkonadiFakeData data;
0106 
0107         // Two top level collections, one with tasks, one with notes
0108         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0109         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent());
0110 
0111         // One with a note child collection
0112         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent());
0113 
0114         // One with a task child collection
0115         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteChild")).withParent(44).withNoteContent());
0116 
0117         // WHEN
0118         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0119                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0120                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0121 
0122         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel();
0123         result->data();
0124         result = queries->findTopLevel(); // Should not cause any problem or wrong data
0125 
0126         // THEN
0127         QVERIFY(result->data().isEmpty());
0128         TestHelpers::waitForEmptyJobQueue();
0129 
0130         const auto sources = result->data();
0131         auto actualNames = QStringList();
0132         std::transform(sources.constBegin(), sources.constEnd(),
0133                        std::back_inserter(actualNames),
0134                        [] (const Domain::DataSource::Ptr &source) { return source->name(); });
0135         actualNames.sort();
0136 
0137         QCOMPARE(actualNames, QStringList() << "42Task");
0138     }
0139 
0140     void shouldReactToCollectionAddsForTopLevelSources()
0141     {
0142         // GIVEN
0143         AkonadiFakeData data;
0144 
0145         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0146                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0147                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0148 
0149         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel();
0150         TestHelpers::waitForEmptyJobQueue();
0151         QVERIFY(result->data().isEmpty());
0152 
0153         // WHEN
0154         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0155 
0156         // THEN
0157         QCOMPARE(result->data().size(), 1);
0158         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task"));
0159     }
0160 
0161     void shouldReactToCollectionRemovesForTopLevelSources()
0162     {
0163         // GIVEN
0164         AkonadiFakeData data;
0165 
0166         // Two top level collections and two child collections
0167         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0168         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent());
0169         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent());
0170         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("43NoteChild")).withParent(43).withNoteContent());
0171 
0172         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0173                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0174                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0175 
0176         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel();
0177         TestHelpers::waitForEmptyJobQueue();
0178         QCOMPARE(result->data().size(), 1);
0179 
0180         // WHEN
0181         data.removeCollection(Akonadi::Collection(42));
0182         data.removeCollection(Akonadi::Collection(43));
0183 
0184         // THEN
0185         QCOMPARE(result->data().size(), 0);
0186     }
0187 
0188     void shouldReactToItemChangesForTopLevelTasks()
0189     {
0190         // GIVEN
0191         AkonadiFakeData data;
0192 
0193         // Two top level collections and one child collection
0194         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0195         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent());
0196         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteChild")).withParent(43).withNoteContent());
0197 
0198         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0199                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0200                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0201 
0202         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel();
0203 
0204         bool replaceHandlerCalled = false;
0205         result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) {
0206                                           replaceHandlerCalled = true;
0207                                       });
0208         TestHelpers::waitForEmptyJobQueue();
0209         QCOMPARE(result->data().size(), 1);
0210 
0211         // WHEN
0212         data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42TaskBis")));
0213         TestHelpers::waitForEmptyJobQueue();
0214 
0215         // THEN
0216         QCOMPARE(result->data().size(), 1);
0217         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42TaskBis"));
0218         QVERIFY(replaceHandlerCalled);
0219     }
0220 
0221     void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data()
0222     {
0223         QTest::addColumn<int>("colErrorCode");
0224         QTest::addColumn<int>("colFetchBehavior");
0225         QTest::addColumn<bool>("deleteQuery");
0226 
0227         QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0228                                                              << false;
0229 
0230         QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0231                                                              << true;
0232 
0233         QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0234                                                           << false;
0235 
0236         QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0237                                                           << true;
0238 
0239         QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0240                                                     << false;
0241 
0242         QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0243                                                     << true;
0244     }
0245 
0246     void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty()
0247     {
0248         // GIVEN
0249         AkonadiFakeData data;
0250 
0251         // Two top level collections
0252         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0253         data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent());
0254 
0255         std::unique_ptr<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0256                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0257                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0258         QFETCH(int, colErrorCode);
0259         QFETCH(int, colFetchBehavior);
0260         data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode);
0261         data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(),
0262                                                            AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior));
0263 
0264         QFETCH(bool, deleteQuery);
0265 
0266         // WHEN
0267         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel();
0268 
0269         if (deleteQuery)
0270             delete queries.release();
0271 
0272         // THEN
0273         QVERIFY(result->data().isEmpty());
0274         TestHelpers::waitForEmptyJobQueue();
0275         QCOMPARE(result->data().size(), 0);
0276     }
0277 
0278     void shouldLookInAllReportedForChildSources()
0279     {
0280         // GIVEN
0281         AkonadiFakeData data;
0282 
0283         // One top level collection with two children (one of them also having a child)
0284         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0285         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent());
0286         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44TaskFirstChildChild")).withParent(43).withTaskContent());
0287         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent());
0288 
0289         // Serializer
0290         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0291         Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0292 
0293         // WHEN
0294         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0295                                                                                          serializer,
0296                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0297 
0298         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource);
0299         result->data();
0300         result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data
0301 
0302         // THEN
0303         QVERIFY(result->data().isEmpty());
0304         TestHelpers::waitForEmptyJobQueue();
0305 
0306         const auto sources = result->data();
0307         auto actualNames = QStringList();
0308         std::transform(sources.constBegin(), sources.constEnd(),
0309                        std::back_inserter(actualNames),
0310                        [] (const Domain::DataSource::Ptr &source) { return source->name(); });
0311         actualNames.sort();
0312 
0313         QCOMPARE(actualNames, QStringList() << QStringLiteral("43TaskFirstChild"));
0314     }
0315 
0316     void shouldReactToCollectionAddsForChildSources()
0317     {
0318         // GIVEN
0319         AkonadiFakeData data;
0320 
0321         // One top level collection with no child yet
0322         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0323 
0324         // Serializer
0325         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0326         Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0327 
0328         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0329                                                                                          serializer,
0330                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0331 
0332         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource);
0333         result->data();
0334         result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data
0335         TestHelpers::waitForEmptyJobQueue();
0336         QCOMPARE(result->data().size(), 0);
0337 
0338         // WHEN
0339         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent());
0340 
0341         // THEN
0342         QCOMPARE(result->data().size(), 1);
0343         QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskChild"));
0344     }
0345 
0346     void shouldReactToCollectionRemovesForChildSources()
0347     {
0348         // GIVEN
0349         AkonadiFakeData data;
0350 
0351         // One top level collection with two children
0352         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0353         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent());
0354         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent());
0355 
0356         // Serializer
0357         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0358         Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0359 
0360         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0361                                                                                          serializer,
0362                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0363 
0364         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource);
0365         TestHelpers::waitForEmptyJobQueue();
0366         QCOMPARE(result->data().size(), 1);
0367 
0368         // WHEN
0369         data.removeCollection(Akonadi::Collection(44));
0370 
0371         // THEN
0372         QCOMPARE(result->data().size(), 1);
0373         QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChild"));
0374     }
0375 
0376     void shouldReactToCollectionChangesForChildSources()
0377     {
0378         // GIVEN
0379         AkonadiFakeData data;
0380 
0381         // One top level collection with two children
0382         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0383         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent());
0384         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent());
0385 
0386         // Serializer
0387         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0388         Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0389 
0390         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0391                                                                                          serializer,
0392                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0393 
0394         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource);
0395         bool replaceHandlerCalled = false;
0396         result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) {
0397                                           replaceHandlerCalled = true;
0398                                       });
0399         TestHelpers::waitForEmptyJobQueue();
0400         QCOMPARE(result->data().size(), 1);
0401 
0402         // WHEN
0403         data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskFirstChildBis")));
0404 
0405         // THEN
0406         TestHelpers::waitForEmptyJobQueue();
0407         QCOMPARE(result->data().size(), 1);
0408         QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChildBis"));
0409         QVERIFY(replaceHandlerCalled);
0410     }
0411 
0412     void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty_data()
0413     {
0414         QTest::addColumn<int>("colErrorCode");
0415         QTest::addColumn<int>("colFetchBehavior");
0416         QTest::addColumn<bool>("deleteQuery");
0417 
0418         QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0419                                                              << false;
0420 
0421         QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0422                                                              << true;
0423 
0424         QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0425                                                           << false;
0426 
0427         QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0428                                                           << true;
0429 
0430         QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0431                                                     << false;
0432 
0433         QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0434                                                     << true;
0435     }
0436 
0437     void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty()
0438     {
0439         // GIVEN
0440         AkonadiFakeData data;
0441 
0442         // One top level collection with two children
0443         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent());
0444         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent());
0445         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent());
0446 
0447         // Serializer
0448         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0449         Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0450 
0451         QFETCH(bool, deleteQuery);
0452         QFETCH(int, colErrorCode);
0453         QFETCH(int, colFetchBehavior);
0454         data.storageBehavior().setFetchCollectionsErrorCode(data.collection(42).id(), colErrorCode);
0455         data.storageBehavior().setFetchCollectionsBehavior(data.collection(42).id(),
0456                                                            AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior));
0457 
0458         // WHEN
0459         std::unique_ptr<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0460                                                                                          serializer,
0461                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0462 
0463         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource);
0464 
0465         if (deleteQuery)
0466             delete queries.release();
0467 
0468         // THEN
0469         QVERIFY(result->data().isEmpty());
0470         TestHelpers::waitForEmptyJobQueue();
0471         QCOMPARE(result->data().size(), 0);
0472     }
0473 
0474     void shouldLookInAllReportedForSelectedSources()
0475     {
0476         // GIVEN
0477         AkonadiFakeData data;
0478 
0479         // Two top level collections, one with tasks, one with notes and two child collections
0480         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false));
0481         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true));
0482         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false));
0483         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true));
0484 
0485         // WHEN
0486         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0487                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0488                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0489 
0490         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected();
0491         result->data();
0492         result = queries->findAllSelected(); // Should not cause any problem or wrong data
0493 
0494         // THEN
0495         QVERIFY(result->data().isEmpty());
0496         TestHelpers::waitForEmptyJobQueue();
0497 
0498         const auto sources = result->data();
0499         auto actualNames = QStringList();
0500         std::transform(sources.constBegin(), sources.constEnd(),
0501                        std::back_inserter(actualNames),
0502                        [] (const Domain::DataSource::Ptr &source) { return source->name(); });
0503         actualNames.sort();
0504 
0505         QCOMPARE(actualNames, QStringList() << QStringLiteral("42Task ยป 43Task"));
0506     }
0507 
0508     void shouldReactToCollectionAddsForSelectedSources()
0509     {
0510         // GIVEN
0511         AkonadiFakeData data;
0512 
0513         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0514                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0515                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0516 
0517         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected();
0518         TestHelpers::waitForEmptyJobQueue();
0519         QVERIFY(result->data().isEmpty());
0520 
0521         // WHEN
0522         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false));
0523         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true));
0524         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false));
0525         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true));
0526 
0527         // THEN
0528         QCOMPARE(result->data().size(), 1);
0529         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task ยป 43Task"));
0530     }
0531 
0532     void shouldReactToCollectionRemovesForSelectedSources()
0533     {
0534         // GIVEN
0535         AkonadiFakeData data;
0536 
0537         // Two top level collections and two child collections
0538         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false));
0539         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true));
0540         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false));
0541         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true));
0542 
0543         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0544                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0545                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0546 
0547         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected();
0548         TestHelpers::waitForEmptyJobQueue();
0549         QCOMPARE(result->data().size(), 1);
0550 
0551         // WHEN
0552         data.removeCollection(Akonadi::Collection(43));
0553         data.removeCollection(Akonadi::Collection(45));
0554 
0555         // THEN
0556         QCOMPARE(result->data().size(), 0);
0557     }
0558 
0559     void shouldReactToCollectionChangesForSelectedSources()
0560     {
0561         // GIVEN
0562         AkonadiFakeData data;
0563 
0564         // Two top level collections and one child collection
0565         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false));
0566         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true));
0567         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false));
0568         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true));
0569 
0570         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0571                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0572                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0573 
0574         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected();
0575 
0576         bool replaceHandlerCalled = false;
0577         result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) {
0578                                           replaceHandlerCalled = true;
0579                                       });
0580         TestHelpers::waitForEmptyJobQueue();
0581         QCOMPARE(result->data().size(), 1);
0582 
0583         // WHEN
0584         data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskBis")));
0585         data.modifyCollection(GenCollection(data.collection(45)).withName(QStringLiteral("45NoteBis")));
0586         TestHelpers::waitForEmptyJobQueue();
0587 
0588         // THEN
0589         QCOMPARE(result->data().size(), 1);
0590         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task ยป 43TaskBis"));
0591         QVERIFY(replaceHandlerCalled);
0592     }
0593 
0594     void shouldNotCrashDuringFindAllSelectedWhenFetchJobFailedOrEmpty_data()
0595     {
0596         QTest::addColumn<int>("colErrorCode");
0597         QTest::addColumn<int>("colFetchBehavior");
0598         QTest::addColumn<bool>("deleteQuery");
0599 
0600         QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0601                                                              << false;
0602 
0603         QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0604                                                              << true;
0605 
0606         QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0607                                                           << false;
0608 
0609         QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch)
0610                                                           << true;
0611 
0612         QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0613                                                     << false;
0614 
0615         QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch)
0616                                                     << true;
0617     }
0618 
0619     void shouldNotCrashDuringFindAllSelectedWhenFetchJobFailedOrEmpty()
0620     {
0621         // GIVEN
0622         AkonadiFakeData data;
0623 
0624         // Two top level collections and two child collections
0625         data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false));
0626         data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true));
0627         data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false));
0628         data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true));
0629 
0630         std::unique_ptr<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0631                                                                                          Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer),
0632                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0633         QFETCH(int, colErrorCode);
0634         QFETCH(int, colFetchBehavior);
0635         data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode);
0636         data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(),
0637                                                            AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior));
0638 
0639         QFETCH(bool, deleteQuery);
0640 
0641         // WHEN
0642         Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected();
0643 
0644         if (deleteQuery)
0645             delete queries.release();
0646 
0647         // THEN
0648         QVERIFY(result->data().isEmpty());
0649         TestHelpers::waitForEmptyJobQueue();
0650         QCOMPARE(result->data().size(), 0);
0651     }
0652 
0653     void shouldLookInCollectionForProjects()
0654     {
0655         // GIVEN
0656         AkonadiFakeData data;
0657 
0658         // Two top level collections
0659         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0660         data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent());
0661 
0662         // One project in the first collection
0663         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject());
0664 
0665         // Two projects in the second collection
0666         data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43")).asProject());
0667         data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).asProject());
0668 
0669         // WHEN
0670         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0671         std::unique_ptr<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0672                                                                                          serializer,
0673                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0674         Domain::DataSource::Ptr dataSource1 = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0675         auto result1 = queries->findProjects(dataSource1);
0676         result1->data();
0677         result1 = queries->findProjects(dataSource1); // Should not cause any problem or wrong data
0678 
0679         // THEN
0680         QVERIFY(result1->data().isEmpty());
0681         TestHelpers::waitForEmptyJobQueue();
0682 
0683         QCOMPARE(result1->data().size(), 1);
0684         QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42"));
0685 
0686         // WHEN
0687         Domain::DataSource::Ptr dataSource2 = serializer->createDataSourceFromCollection(data.collection(43), Akonadi::SerializerInterface::BaseName);
0688         auto result2 = queries->findProjects(dataSource2);
0689         result2->data();
0690         result2 = queries->findProjects(dataSource2); // Should not cause any problem or wrong data
0691 
0692         // THEN
0693         QVERIFY(result2->data().isEmpty());
0694         TestHelpers::waitForEmptyJobQueue();
0695 
0696         QCOMPARE(result1->data().size(), 1);
0697         QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42"));
0698 
0699         QCOMPARE(result2->data().size(), 2);
0700         QCOMPARE(result2->data().at(0)->name(), QStringLiteral("43"));
0701         QCOMPARE(result2->data().at(1)->name(), QStringLiteral("44"));
0702     }
0703 
0704     void shouldIgnoreItemsWhichAreNotProjects()
0705     {
0706         // GIVEN
0707         AkonadiFakeData data;
0708 
0709         // One top level collection
0710         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0711 
0712         // One project and one regular task in the collection
0713         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject());
0714         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")));
0715 
0716         // WHEN
0717         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0718         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0719                                                                                          serializer,
0720                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0721         Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0722         auto result = queries->findProjects(dataSource);
0723         QVERIFY(result->data().isEmpty());
0724         TestHelpers::waitForEmptyJobQueue();
0725 
0726         // THEN
0727         QCOMPARE(result->data().size(), 1);
0728         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42"));
0729     }
0730 
0731     void shouldReactToItemAddsForProjectsOnly()
0732     {
0733         // GIVEN
0734         AkonadiFakeData data;
0735 
0736         // One empty collection
0737         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0738 
0739         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0740         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0741                                                                                          serializer,
0742                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0743         Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0744         auto result = queries->findProjects(dataSource);
0745         TestHelpers::waitForEmptyJobQueue();
0746         QVERIFY(result->data().isEmpty());
0747 
0748         // WHEN
0749         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject());
0750         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")));
0751 
0752         // THEN
0753         QCOMPARE(result->data().size(), 1);
0754         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42"));
0755     }
0756 
0757     void shouldReactToItemRemovesForProjects()
0758     {
0759         // GIVEN
0760         AkonadiFakeData data;
0761 
0762         // One top level collection
0763         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0764 
0765         // Three projects in the collection
0766         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject());
0767         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject());
0768         data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject());
0769 
0770         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0771         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0772                                                                                          serializer,
0773                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0774         Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0775         auto result = queries->findProjects(dataSource);
0776         TestHelpers::waitForEmptyJobQueue();
0777         QCOMPARE(result->data().size(), 3);
0778 
0779         // WHEN
0780         data.removeItem(Akonadi::Item(43));
0781 
0782         // THEN
0783         QCOMPARE(result->data().size(), 2);
0784         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42"));
0785         QCOMPARE(result->data().at(1)->name(), QStringLiteral("44"));
0786     }
0787 
0788     void shouldReactToItemChangesForProjects()
0789     {
0790         // GIVEN
0791         AkonadiFakeData data;
0792 
0793         // One top level collection
0794         data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent());
0795 
0796         // Three projects in the collection
0797         data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject());
0798         data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject());
0799         data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject());
0800 
0801         auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer);
0802         QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Ptr(data.createStorage()),
0803                                                                                          serializer,
0804                                                                                          Akonadi::MonitorInterface::Ptr(data.createMonitor())));
0805         Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName);
0806         auto result = queries->findProjects(dataSource);
0807         // Even though the pointer didn't change it's convenient to user if we call
0808         // the replace handlers
0809         bool replaceHandlerCalled = false;
0810         result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Project::Ptr &, int) {
0811                                           replaceHandlerCalled = true;
0812                                       });
0813         TestHelpers::waitForEmptyJobQueue();
0814         QCOMPARE(result->data().size(), 3);
0815 
0816         // WHEN
0817         data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis")));
0818 
0819         // THEN
0820         QCOMPARE(result->data().size(), 3);
0821         QCOMPARE(result->data().at(0)->name(), QStringLiteral("42"));
0822         QCOMPARE(result->data().at(1)->name(), QStringLiteral("43bis"));
0823         QCOMPARE(result->data().at(2)->name(), QStringLiteral("44"));
0824         QVERIFY(replaceHandlerCalled);
0825     }
0826 };
0827 
0828 ZANSHIN_TEST_MAIN(AkonadiDataSourceQueriesTest)
0829 
0830 #include "akonadidatasourcequeriestest.moc"